معالجة الاستثنائات Exception Handling
السلام عليكم ورحمة الله وبركاته
مرحبا اخواني واخواتي بكم في هذه الجزء من اجزاء السي ++ , سنتكلم اليوم عن معالجة الاستثناءات Exception Handling وهي احد المواضيع الحساسة التي يجب ان يتقنها كل مبرمج على وجه العموم ,
سأقوم بتقسيم درسي إلى عدة اجزاء ابدا بها من الصفر متدرجا لاعلى لاصل إلى ماسأصل إليه , وعلى بركة الله نبدأ :
-----------------------------------------------------------------------------------------------------------------------
1- نظرة عامة
لقد جرى تصميم اسلوب معالجة الاستثناءات للتعامل مع الاخطاء المتزامنة Synchronous errors مثل محاولة القسمة على الصفر , فعند استخدام اسلوب معالجة الاستثناءات يقوم البرنامج بالتحقق من القيمة المدخلة قبل اجراء القسمة من خلال اجراء استثناء اذا كانت هذه القيمة مساوية للصفر .
تفيد تقنية معالجة الاستثناءات في التعامل مع المشاكل التي تحدث اثناء تفاعل البرنامج مع العناصر البرمجية الاخرى مثل : دوال البناء Constractor ودوال الهدم Destractor ...إلخ , فبدلا من قيام هذه العناصر البرمجية بمعالجة الاخطاء داخليا فإنها تستخدم الاستثناءات لإعلام البرنامج بحدوث المشاكل , يساعد ذلك المبرمجين على بناء اسلوب منفصل للتعامل مع الاخطاء من اجل كل تطبيق .
2- التعامل مع الاستثناءات :
توفر لغة C++ الكتل Try لمعالجة الاستثناءات , وتتألف الكتلة من الكلمة المفتاحية Try متبوعة بـ { } والذان يتضمنان تعريف الكتلة والاستثناءات التي يمكن ان تحصل.
بعد ذلك نستخدم كتلة catch اذا تضمن الاستثناء على وسيط فيجب ان نستخدم كلمة catch للاستقبال وللتفاعل مع هذا الاستثناء .
نستخدم الكلمة throw من اجل اللقاء الاستثناء متبوعة بنوع الاستثناء كمعامل لها .
مثال :
هذا البرنامج يأخذ رقين ويخرج حاصل قسمتهم , سنتأكد من ان الرقم الثاني ليس صفر
int x,y;
cin>>x>>y;
try{
if(y==0)
throw;
cout<<x/y;}
catch(...){ }هنا ستكون النتائج على شكل مربع حوار يحتوي على الازرار : تجاهل , اعادة , احباط .
كتابة كلمة throw فقط لوحدها يستدعي المدمر ويقفل البرنامج , اما اذا عدلنا كتابة البرنامج السابق وجعلناه يلقي سلسلة حرفية مثلا :
throw "Time1";
سيكون الناتج لا شئ لعدم كتابة اي تعليمات في كتلة catch , لنقم بطباعة جملة داخل كتلة catch مثلا :
catch(...){cout<<"Exception Handling"<<endl; }سنلاحظ الان اذا قمنا بإدخال الصفر , سيتم القاء استثناء وتقوم كتلة catch باستقبال الاستثناء وتنفيذ التعليمات وهي طباعة الجملة Exception Handling .
تعد السلسة الحرفية "Time1" بمثابة الخطأ الذي تلقيه throw وبما انه لا يوجد كتلة catch تتعامل مع النصوص , فسيتم استدعاء كتلة catch العامة .
اذا عدلنا كتلة catch لتتعامل مع السلاسل الحرفية:
catch(char * x){cout<<"Exception Handling:"<<x<<endl;فسيكون الناتج Exception Handling :Time1 .
يمكن لنا ان نحدد اكثر من كتلة لـ catch تتعامل مع استثناءات مختلفة .
سنقوم الان بتطوير البرنامج السابق وهذه المرة سيقوم باختبار الرقمين , اذا كان الرقم الاول اكبر من 10 يكون عبارة عن استثناء , يطبع لنا More واذا كان الرقم الثاني صفر يطبع لنا Zero :
int x,y;
cin>>x>>y;
try{
if(y==0)
throw "Zero";
if(x>10)
throw x;
cout<<x/y;}
catch(char * x){cout<<"Exception Handling:"<<x<<endl; }
catch(int x){cout<<"Exception Handling:"<<x<<"More than 10"<<endl; }بقي ان اتطرق إلى الدالة exceptions الموجودة في ملف iostream وهي تقوم بمعرفة الخطأ بمجرد ادخال بيانات في متغير لا يقبل ذلك , كأدخال اسم في متغير عدد صحيح int
int x;
cin.exceptions(cin.failbit);
try
{
cin>>x;
cout<<x;
}
catch(...){cerr<<"This Is Not Number"<<endl;}-----------------------------------------------------------------------------------------------------------------------
استخدام OOP
فيما سبق : لو اعاد throw سلسلة نصية وكانت catch تتعامل مع النصوص وتؤدي غرض معين مثل طباعة جملة على الشاشة , فلن نستيع مثلا ان نعيد سلسلة نصية اخرى لتتعامل معها كتلة catch جديدة.
على سبيل المثال :
throw "Zero"; throw "More Than 10"
كليهما سيتعاملان مع
catch(char * x)
لكي نفرق بينهم كنا نستخدم انواع مختلفة مع throw مثلا في مثالنا السابق كنا استخدمنا مع throw للرقم الثاني سلسلة نصية وللرقم الاول متغير عددي
throw "Zero"; throw x;
عندها ستتعامل كتلة catch مع النوعية الملقاة من قبل throw , من خلال هذه الطريقة نستطيع ان نعيد قيم معدودة من الاستثناءات , مثل char , int , float , double ..إلخ
ولكي نجد حلا يجب ان نعرف كائنات Object تنتمي إلى انواع جديدة غير الانواع التقليدية المعرفة مسبقا .
استخدام الكائنات:
سنقوم اولا بكتابة الصنف class الذي يمثل لنا مجموعة الاستثناءات في هذا البرنامج
class DZero
{
public:
DZero(int =0,int=0);
void Print_Divive();
class Zero{};
class More{};
private:
int x,y;
[code]DZero::DZero(int a,int b):x(a),y(b){}
void DZero::Print_Divive()
{
cerr<<x/y<<endl;
}داخل البرنامج الرئيسي , تقوم كتلة try تقوم بإلقاء عدت استثنائات من الاستثنائات الموجودة في الصنف الرئيسي DZero على حسب الشروط المطروحة , إما اذا كان الرقم الاول اكبر من 10 , او اذا كان الرقم الثاني يساوي الصفر .
try
{
cin>>x>>y;
if(x>10)
throw DZero::More();
if(y==0)
throw DZero::Zero();
DZero d(x,y);
d.Print_Divive();
}الجوهر الوحيد يكمن في كتل catch والانواع التي تستقبلها لتنفيذ تعليماتها ,
catch(DZero::More)
{cout<<"More"<<endl;}
catch(DZero::Zero)
{cout<<"Zero"<<endl;}نلاحظ هنا ان catch استقبلت استثنائات مختلفة في كل مرة , من خلال هذه الطريقة نستطيع ان نتخلص من الانواع التقليدية المعرفة داخل اللغة ونصمم مانشاء من الانواع حسب الحاجة .
كما نستطيع ان نعرف دوال داخل الاصناف More & Zero ونستدعيها لتقوم بتنفيذ تعليمات مختلفة , مثلا :
class DZero
{
public:
DZero(int =0,int=0);
void Print_Divive();
class Zero{};
class More
{
public:
void Print();
};
private:
int x,y;
};وان نعيد نجعل الدالة Print الخاصة بالصنف More تقوم بطباعة الجملة More
void DZero::More::Print()
{
cout<<"More"<<endl;
}
DZero::DZero(int a,int b):x(a),y(b){}
void DZero::Print_Divive()
{
cerr<<x/y<<endl;
}في هذه الحالة سنقوم بتعديل catch الخاصة باستقبال الاستثناء more إلى
catch(DZero::More a)
{
a.Print();
}----------------------------------------------------------------------------------------------------------------------
اخير سنأخذ فكرة عن الملف الرأسي exception وسنزيد في التفصيل من خلال الدروس القادمة
سأقوم بكتابة نفس المثال السابق , القسمة على الصفر , ولكن هذه المرة بالاستفادة من الملف exception
نقوم باستدعاء الملف
#include <exception> using std::exception;
نقوم بإنشاء صنف جديد DZero بالاشتقاق من الصنف exception
class DZero :public exception
{
public:
DZero::DZero()
: exception( "Error : Zero" ) {}
};في داخل try نلقي الاستثناء كما تعودنا من نوع DZero
try
{
cin>>x>>y;
if(y==0)
throw DZero();
cout<<x/y;
}وكتلة catch تقوم باستقبال الاستثناء وتقوم بطباعة الجملة Error:Zero من خلال استدعاء الدالة What
catch(DZero & d)
{cout<<d.what()<<endl;}إلى هنا اكون قد اعطيت فكرة عامة عن الاستثناءات , في المرة القادمة سأتدرج إلى مستوى اعلى وسنتحدث عن :
معالجة الاستثناءات الغير متوقعة .
إفراغ مكدس الاستدعاءات .
معالجة اخفاقات new
الصف auto_ptr والحجز الديناميكي للذاكرة .
التعامل مع الملفات والمصادر .
امثلة.
تمنياتي بالتوفيق
