مؤشرات الملفات في ++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);
يعني تحديد الموضع بعشر بايتات من نهاية الملف ] لهذا كانت الإزاحة سالبة[ ويبين الشكل عمل مثل هذه الدالة في حالة المعامل الواحد والشكل حالة المعاملين .
وفي المثال التالي نستخدم إمكانية تحديد الموضع , باستخدام صورة المعامل الواحد للدالة , للوصول المباشر للكائن المطلوب .
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 ) قبل كتابة البيانات مباشرة . يقرأ هذا البيان قبل قراءة الكائن , ثم ننشئ كائنا بنفس النوع , ننسخ فيه بيانات الكائن المقروء من الكلف .
ومن الممكن زيادة كفاءة البرنامج بإضافة دوال للمحو ولقراءة موظف بعينه والبحث طبقا لصفة وغير ذلك من أفكار .
تعليقات
إرسال تعليق