مؤشرات الملفات في ++C

مؤشرات الملفات

كل كائن لملف يتضمن رقمين , يسميان " مؤشر القراءة ومؤشر الكتابة get pointer , put pointer " , كما قد يسميا current get position موضع القراءة الحالة " , و current put position " موضع الكتابة الحالي " , أو اختصارا current position " الوضع الحالي " يحدد الرقمان بداية القراءة والكتابة .

والوضع الغالب للقراءة هي من أول الملف إلي نهايته , أما الكتابة فقد يقصد أن تكون من بداية الملف بحيث تمحو الكتابة الجديدة القديمة , أو من نهاية الملف , وذلك في حالة الإلحاق حيث تفتح الملف مستخدمين البتة ios::app .

ولكن قد تثور الحاجة لتحديد موضع معين للكتابة أو للقراءة عندئذ نريد التحكم في مؤشرات الموضع , عندئذ نستخدم الدوال التي تمكننا من ذلك , وهي الدالتان seekg(),tellg() , وseekp(), tellp() للكتابة , كما سوف نشرح فيما يلي .

تحديد المواضع

قدمنا لمثال لتحديد بالدالة seekg() عن طريق معامل وحيد , يحدد الموضع في بداية الملف . ولهذه الدالة حالة أخري , يكون لها معاملان , أحدهما يحدد مسافة يقال لها " الإزاحة offset " والثاني يحدد النقطة المنسوب لها الإزاحة ولها ثلاثة احتمالات :

Beg في حالة بداية الملف , و cur الموضع الحالي , و end نهاية الملف . فمثلا :

Seekg(-10, ios::end);

يعني تحديد الموضع بعشر بايتات من نهاية الملف ] لهذا كانت الإزاحة سالبة[ ويبين الشكل عمل مثل هذه الدالة في حالة المعامل الواحد والشكل حالة المعاملين .

Object-Oriented Programming in C   _Page_0624_Image_0001

Object-Oriented Programming in C   _Page_0625_Image_0001

وفي المثال التالي نستخدم إمكانية تحديد الموضع , باستخدام صورة المعامل الواحد للدالة , للوصول المباشر للكائن المطلوب .

seekg.cpp


 


// seekg.cpp


// seeks particular person in file


// UCS Laboratories


 


#include <fstream.h>              // for file streams


#include <conio.h>


 


class person                      // class of persons


   {


   protected:


      char name[40];              // person's name


      int age;                    // person's age


   public:


      void showData(void)         // display person's data


     {


     cout << "\n   Name: " << name;


         cout << "\n   Age: " << age;


     }


   };


 


void main(void)


   {


   person pers;                   // create person object


   ifstream infile;               // create input file


   infile.open("PERSON.DAT", ios::binary);  // open file


 


   infile.seekg(0, ios::end);     // go to 0 bytes from end


   int endposition = infile.tellg();        // find where we are


   int n = endposition / sizeof(person);    // number of persons


   cout << "\nThere are " << n << " persons in file";


 


   cout << "\nEnter person number: ";


   cin >> n;


   int position = (n-1) * sizeof(person);  // number times size


   infile.seekg(position);        // bytes from begin


                  // read one person


   infile.read( (char*)&pers, sizeof(pers) );


   pers.showData();               // display the person


   getche();


   }




تحديد موضع نهاية الملف



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



Infile.seekg(0, ios::end);



ثم نستخدم الدالة tellg() , وهي تعيد النقطة الحالية من الملف , ونعطيها المتغير المخصص لنقطة نهاية الملف وذلك كما يلي :



Int end position = infile.tellg();



بهذا الأمر تتغير القيمة المخزنة في المتغير endposition لتعبر دائما عن نهاية الملف . أما حساب عدد الكائنات المدخلة (المتغير n ) فتكون بقسمة قيمة هذا المتغير علي حجم بيانات كل كائن , كما تعيده الدالة sizeof(person) وباستخدام المتغير n يمكن حساب الكائن المطلوب .



التعامل مع الأخطاء



افترضنا إلي أن العمليات سوف تتم بلا مشاكل الملف المطلوب قراءته موجود بالفعل أو الاسم المختار لملفنا ليس معطي لملف موجود بالفعل أو حتي وجود ما يمنع العملية لسبب متعلق بالمونات المادية , أبسطها أن يكون باب مشغل الأقراص مفتوحا .



وفي المثال التالي نقوم بمتابعة هذه الاحتمالات .






rewerr.cpp


 


// rewerr.cpp


// handles errors during input and output


// UCS Laboratories


 


#include <fstream.h>     // for file streams


#include <process.h>     // for exit()


#include <conio.h>


 


const int MAX = 1000;


int buff[MAX];


 


void main()


   {


   for(int j=0; j<MAX; j++)            // fill buffer with data


      buff[j] = j;


 


   ofstream os;                        // create output stream


                                       // open it


   os.open("a:edata.dat", ios::trunc | ios::binary);


   if(!os)


      { cerr << "\nCould not open output file"; exit(1); }


 


   cout << "\nWriting...";             // write buffer to it


   os.write( (char*)buff, MAX*sizeof(int) );


   if(!os)


      { cerr << "\nCould not write to file"; exit(1); }


   os.close();                         // must close it


 


   for(j=0; j<MAX; j++)                // clear buffer


      buff[j] = 0;


 


 


   ifstream is;                        // create input stream


   is.open("a:edata.dat", ios::binary);


   if(!is)


      { cerr << "\nCould not open input file"; exit(1); }


 


 


   cout << "\nReading...";             // read file


   is.read( (char*)buff, MAX*sizeof(int) );


   if(!is)


      { cerr << "\nCould not read from file"; exit(1); }


 


   for(j=0; j<MAX; j++)                // check data


      if( buff[j] != j )


         { cerr << "\nData is incorrect"; exit(1); }


   cout << "\nData is correct";


   getche();


   }




تحليل الأخطاء



في البرنامج السابق كان بإمكاننا الحصول علي رسالة بحدوث خطأ في عمليتي الكتابة أو القراءة بصرف النظر عن السبب الذي تسبب فيه .



وفي المثال التالي سوف تدفع بالفكرة قدما لنعطي إمكانية تحليل الخطأ عن طريق قرائة بتات أخطاء التدفق .






ferrors.cpp


 


// ferrors.cpp


// checks for errors opening file


#include <fstream.h>          // for file functions


#include <conio.h>


 


void main()


   {


   ifstream file;


   file.open("GROUP.DAT", ios::nocreate);


 


   if( !file )


      cout << "\nCan't open GROUP.DAT";


   else


      cout << "\nFile opened successfully.";


   cout << "\nfile = " << file;


   cout << "\nError state = " << file.rdstate();


   cout << "\ngood() = " << file.good();


   cout << "\neof() = " << file.eof();


   cout << "\nfail() = " << file.fail();


   cout << "\nbad() = " << file.bad();


   file.close();


   getche();


   }




يختبر البرنامج قيمة الكائن file أو بالشرط :



If( !file)



فإن كانت صفرا , دل ذلك علي حدوث خطأ ما , وظهرت الرسالة المبينة . ثم أعادت الدالة rdstate() الرقم المعبر عن حالة الخطأ "4" , وهو مقابل لعدم وجود الملف . بعد ذلك صدرت عدة رسائل تبين حالات بتات الأخطاء (راجع الشكل ) , فكانت بتة نهاية الملف صفرا , حيث لم نصل لنهاية الملف , وبتة good صفرا حيث أن خطأ قد وقع البرامج الهامة , يجب استخدام مثل هذه الدوال بعد كل عملية إدخال أو إخراج للملفات لمتابعة الأحداث .



العمليات بواسطة الدوال المنتمية



اعتمدنا إلي الأن علي الدالة الأصلية main() في القيام بكافة أعمال القراءة والكتابة من وإلي الملفات وسوف نقدم إليك مثالين , أحدهما يتضمن كائنات تضم دوال خاصة بقراءتها وكتابتها والثاني يتضمن دوال استاتيكية لإجراء العمليات علي كافة كائنات الفئة .



الكائنات ذاتية العمل



يعمل هذا الأسلوب المبسط في حالة كون عدد الكائنات محدودا , وفي المثال التالي سوف نضيف دالتين منتميتين diskln(), diskOut() للفئة person , تمكن الكائنات من القيام بعمليات الملفات ذاتيا .



وقد وضع البرنامج علي أساس فروض للتبسيط : أولا افترضنا أن كافة الكائنات سوف تسجل علي نفس الملف person.dat . ثانيا , أن كل كائن جديد سوف يضاف لنهاية الملف . و حتي نمنع احتمال محاولة قراءة بيانات بعد نهاية الملف , أضفنا دالة استاتيكية diskCount() تعيد عدد الأشخاص الذين يتم إدخالهم .








rewobj.cpp


 


// rewobj.cpp


// person objects do disk I/O


// UCS Laboratories


 


#include <fstream.h>             // for file streams


#include <conio.h>


 


class person                     // class of persons


   {


   protected:


      char name[40];             // person's name


      int age;                   // person's age


   public:


      void getData(void)         // get person's data


     {


     cout << "\n   Enter name: "; cin >> name;


     cout << "   Enter age: "; cin >> age;


     }


      void showData(void)        // display person's data


     {


     cout << "\n   Name: " << name;


         cout << "\n   Age: " << age;


     }


      void diskIn(int);          // read from file


      void diskOut();            // write to file


      static int diskCount();    // return number of


                                 //    persons in file


   };


 


void person::diskIn(int pn)      // read person number pn


   {                             // from file


   ifstream infile;                           // make stream


   infile.open("PERSON.DAT", ios::binary);    // open it


   infile.seekg( pn*sizeof(person) );         // move file ptr


   infile.read( (char*)this, sizeof(*this) ); // read one person


   }


 


void person::diskOut()           // write person to end of file


   {


   ofstream outfile;             // make stream


                                 // open it


   outfile.open("PERSON.DAT", ios::app | ios::binary);


   outfile.write( (char*)this, sizeof(*this) ); // write to it


   }


 


int person::diskCount()          // return number of persons


   {                             // in file


   ifstream infile;


   infile.open("PERSON.DAT", ios::binary);


   infile.seekg(0, ios::end);    // go to 0 bytes from end


                                 // calculate number of persons


   return (int)infile.tellg() / sizeof(person);


   }


 


void main(void)


   {


   person p;                     // make an empty person


   char ch;


 


   do                            // save persons to disk


      {


      cout << "\nEnter data for person:";


      p.getData();               // get data


      p.diskOut();               // write to disk


      cout << "Do another (y/n)? ";


      cin >> ch;


      }


   while(ch=='y');               // until user enters 'n'


 


   int n = person::diskCount();  // how many persons in file?


   cout << "\nThere are " << n << " persons in file";


   for(int j=0; j<n; j++)        // for each one,


      {


      cout << "\nPerson #" << j; 


      p.diskIn(j);               // read person from disk


      p.showData();              // display person


      }


   getche();


   }



وأغلب أجزاء البرنامج مألوفة لك , ولكن تلاحظ أن كافة دوال العمليات غير مرئية للدالة الأصلية , فقد ضمنت في الفئة الأساسية .



ولما كنا لا نعرف مقدما موضع بيانات الأشخاص المدخلين , حيث توضع الكائنات في مواضع متفرقة علي القرص , فإننا استخدمنا المؤشر this في دالتي القراءة والكتابة , والذي سوف يدلهما علي موضع الكائن الحالي لهما . وفي نفس الوقت يكون حجم الكائن هو sizeof(*this) .



ويمكنك بشئ من التعديل , أن تجعل المستخدم يختار اسم الملف ويدخله , ويلزمك في هذه الحالة متغيرا استاتيكيا منتميا للفئة ( وليكن (filename[] , ودالة استاتيكية تتقبل الاسم فيه . كما قد تود أن تجعل لكل كائن اسم ملف يحمل اسمه الخاص , باستخدام دالة غير استاتيكية .



الفئات ذاتية العمل



لنفرض أن لدينا عددا كبيرا من الكائنات , مختزنة في الذاكرة , ونريد أن نكتبها جميعها في ملف . في حالة كهذه , ليس من الكفاءة في شئ أن يكون لكل كائن دالة , فيفتح الملف لكتابة كائن واحد ثم يغلق . علينا أن نفكر في وسيلة يفتح بها الملف لكتابة كافة الكائنات دفعة واحدة قبل إغلاقه .



الدوال والبيانات الاستاتيكية



يمكننا أن نضمن الفئة دالة استاتيكية تتعامل مع كافة كائنات الفئة , فيمكنها بذلك أن تتولي كتابتها في الملف دفعة واحدة , ولن , كيف تعلم بتلك الكائنات ؟ لتحقيق ذلك يمكننا إنشاء مصفوفة مؤشرات استاتيكية أيضا , يخزن بها عنوان كل كائن جديد ينشأ ومن خلال دوارة تستطيع الدالة الاستاتيكية التعامل مع كافة الكائنات .



و حتي ندفع بالبرنامج قدما سوف نفترض أن الكائنات في حد ذاتها مختلفة الحجوم , لما هذا الافتراض ؟ إنه يواجه حالة فئات مشتقة من الفئة الأساسية , علي غرار حالة البرنامج emplpyrr.cpp حيث تختلف الفئات المشتقة (مديرين , باحثين , عاملين ) في حجومها .



إن محاولة معرفة حجم الكائن عن طريق الدالة sizeof() غير مجدية في حالة الفئات المشتقة , حيث إنها ليست دالة اعتبارية . وسوف تعيد في كافة الأحوال حجم كائنات الفئة الأساسية , الهم إلا إذا أعطيناها وسيلة تعرف بها نوع الكائن . هنا نتذكر صديقتنا القديمة , الدالة ttpeid() التي عرضنا لها في الفصل السابق , فهي تعيد لنا فئة الكائن , وهذه المعلومة هي التي نستخدمها للدالة sizeof() .



ونقدم لك برنامجا طموحا يتضمن كافة هذه الأفكار , ويمكن أن يكون أساس لقاعدة بيانات قوية , وتتجلي فيه قوة الأسلوب الكائني في البرمجة . فكيف يمكنك بغيره أن تدخل كائنات بحجوم مختلفة إلي ملف ؟



 




empl_io.cpp


 


// empl_io.cpp


// performs file I/O on employee objects


// handles different sized objects


// UCS Laboratories


 


#include <fstream.h>          // for file-stream functios


#include <conio.h>            // for getche()


#include <process.h>          // for exit()


#include <typeinfo.h>         // for typeid()


 


const int LEN = 32;           // maximum length of last names


const int MAXEM = 100;        // maximum number of employees


 


enum employee_type {tmanager, tscientist, tlaborer};


 


class employee                // employee class


   {


   private:


      char name[LEN];         // employee name


      unsigned long number;   // employee number


      static int n;           // current number of employees


      static employee* arrap[];  // array of ptrs to emps


   public:


      virtual void getdata()


     {


     cout << "\n   Enter last name: "; cin >> name;


     cout << "   Enter number: ";      cin >> number;


     }


      virtual void putdata()


     {


     cout << "\n   Name: " << name;


     cout << "\n   Number: " << number;


     }


      virtual employee_type get_type();  // get type


      static void add();      // add an employee


      static void display();  // display all employees


      static void read();     // read from disk file


      static void write();    // write to disk file


   };


 


// static variables


int employee::n;              // current number of employees


employee* employee::arrap[MAXEM];  // array of ptrs to emps


 


// manager class


class manager : public employee


   {


   private:


      char title[LEN];        // "vice-president" etc.


      double dues;            // golf club dues


   public:


      void getdata()


     {


         employee::getdata();


     cout << "   Enter title: ";          cin >> title;


     cout << "   Enter golf club dues: "; cin >> dues;


     }


      void putdata()


     {


     employee::putdata();


     cout << "\n   Title: " << title;


     cout << "\n   Golf club dues: " << dues;


     }


   };


 


// scientist class


class scientist : public employee


   {


   private:


      int pubs;               // number of publications


   public:


      void getdata()


     {


     employee::getdata();


     cout << "   Enter number of pubs: "; cin >> pubs;


     }


      void putdata()


     {


     employee::putdata();


     cout << "\n   Number of publications: " << pubs;


     }


   };


   


// laborer class


class laborer : public employee


   {


   };


 


// add employee to list in memory


void employee::add()


   {


   cout << "\n'm' to add a manager"


           "\n's' to add a scientist"


           "\n'l' to add a laborer"


           "\nType selection: ";


   switch( getche() )


      {                       // create specified employee type


      case 'm': arrap[n] = new manager;   break;


      case 's': arrap[n] = new scientist; break;


      case 'l': arrap[n] = new laborer;   break;


      default: cout << "\nUnknown employee type";


      }


   arrap[n++]->getdata();     // get employee data from user


   }


 


// display all employees


void employee::display()


   {


   for(int j=0; j<n; j++)


      {


      cout << '\n' << (j+1);  // display number


      switch( arrap[j]->get_type() )   // display type


         {


         case tmanager:    cout << ". Type: Manager";   break;


         case tscientist:  cout << ". Type: Scientist"; break;


         case tlaborer:    cout << ". Type: Laborer";   break;


         default: cout << ". Unknown type";


         }


      arrap[j]->putdata();    // display employee data


      }


   }


 


// return the type of this object


employee_type employee::get_type()


   {


   if( typeid(*this) == typeid(manager) )


      return tmanager;


   else if( typeid(*this)==typeid(scientist) )


      return tscientist;


   else if( typeid(*this)==typeid(laborer) )


      return tlaborer;


   else


      { cout << "\nBad employee type"; exit(1); }


   return tmanager;


   }


 


// write all current memory objects file


void employee::write()


   {


   int size;


   cout << "\nWriting " << n << " employees.";


   ofstream ouf;              // open ofstream in binary


   employee_type etype;       // type of each employee object


 


   ouf.open("EMPLOY.DAT", ios::trunc | ios::binary);


   if(!ouf)


      { cout << "\nCan't open file"; return; }


   for(int j=0; j<n; j++)     // for every employee object


      {                       // get it's type


      etype = arrap[j]->get_type();


                              // write type to file


      ouf.write( (char*)&etype, sizeof(etype) );


      switch(etype)           // find its size


         {


         case tmanager:   size=sizeof(manager); break;


         case tscientist: size=sizeof(scientist); break;


         case tlaborer:   size=sizeof(laborer); break;


         }                    // write employee object to file


      ouf.write( (char*)(arrap[j]), size );


      if(!ouf)


         { cout << "\nCan't write to file"; return; }


      }


   }


 


// read data for all employees from file into memory


void employee::read()


   {


   int size;                  // size of employee object


   employee_type etype;       // type of employee


   ifstream inf;              // open ifstream in binary


   inf.open("EMPLOY.DAT", ios::binary);


   if(!inf)


      { cout << "\nCan't open file"; return; }


   n = 0;                     // no employees in memory yet


   while(1)


      {                       // read type of next employee


      inf.read( (char*)&etype, sizeof(etype) );


      if( inf.eof() )         // quit loop on eof


         break;


      if(!inf)                // error reading type


         { cout << "\nCan't read type from file"; return; }


      switch(etype)


         {                    // make new employee


         case tmanager:       // of correct type


            arrap[n] = new manager;


            size=sizeof(manager);


            break;


         case tscientist:


            arrap[n] = new scientist;


            size=sizeof(scientist);


            break;


         case tlaborer:


            arrap[n] = new laborer;


            size=sizeof(laborer);


            break;


         default: cout << "\nUnknown type in file"; return;


         }                    // read data from file into it


      inf.read( (char*)arrap[n], size  );


      if(!inf)                // error but not eof


         { cout << "\nCan't read data from file"; return; }


      n++;                    // count employee


      }  // end while


   cout << "\nReading " << n << " employees";


   }


 


 


void main()


//UCS Laboratories


   {


   while(1)


      {


      cout << "\n'a' -- add data for an employee"


              "\n'd' -- display data for all employees"


              "\n'w' -- write all employee data to file"


              "\n'r' -- read all employee data from file"


              "\n'x' -- exit"


              "\nType selection: ";


      switch( getche() )


         {


         case 'a':            // add an employee to list


            employee::add();


            break;


         case 'd':            // display all employees


            employee::display();


            break;


         case 'w':            // write employees to file


            employee::write();


            break;


         case 'r':            // read all employees from file


            employee::read();


            break;


         case 'x':            // exit program


            return;


         default: cout << "\nUnknown command";


         }


      }  // end while


   getche();


   }




رقم كودي لنوع الكائن



نعرف كيف نجد فئة كائن مخزن بالذاكرة , ولكنا لا نعرف كيف نجد فئة لكائن علي وشك أن يقرأ من ملف , وليس لدينا دالة سحرية تقوم بذلك . وعلي ذلك , فإننا حين نكتب بيانات كائن في ملف , نحتاج إلي رقم كودي (المتغير employee_type من النوع emum ) قبل كتابة البيانات مباشرة . يقرأ هذا البيان قبل قراءة الكائن , ثم ننشئ كائنا بنفس النوع , ننسخ فيه بيانات الكائن المقروء من الكلف .



ومن الممكن زيادة كفاءة البرنامج بإضافة دوال للمحو ولقراءة موظف بعينه والبحث طبقا لصفة وغير ذلك من أفكار .

تعليقات

المشاركات الشائعة من هذه المدونة

المؤثرات الحسابية في C++

دوال النمط الرسومي في ++C

المؤثرات المنطقية في C++