Operator OverLoading اعاده تعريف المعاملات أو كما يطلق عليها البعض التحميل الزائد للمعاملات . كما مر علينا من قبل عند دراستنا للدوال Function وعرفنا أنه يمكن للداله أن نعيد تعريفها (تحميل زائد ) Overload بحيث تستقبل وسائط مختلفه من حيث العدد أو النوع ، فإن هذا المفهوم نطبقه أيضا على المعاملات العاديه + ، - ، * ، < الخ ، حيث نعيد تعريف هذه المعاملات حتى تتعامل مع الكائنات وليس مع أنواع بيانات عاديه Primitive . لنوضح أكثر قليلا ، عندما نريد أن نجمع متغيرين من نوع int ، نقوم بتطبيق علامه الجمع + بالشكل التالي : plain text int x = a+ b; لاحظ هنا جمعنا المتغير a مع المتغير b . الان في حاله أردنا جمع كائنين ، فاننا لا نستطيع تطبيق (سينتج خطأ عند الترجمه) : plain text Object x = ObjectA + ObjectB; لأن المعامل + لا يستطيع معرفه ما الذي يجمعه أو كيف سيجمع كائن مع كائن ؟ لذلك علينا (المبرمج) أن يعيد تعريف المعامل + بحيث يتعامل مع البيانات التي بداخل الكائن ويقوم بجمعها أو طرحها أو العمليه التي نريد . هذه هي الفكره من موضوع الـ Operator Overloading أي نجعل هذه الأشارات تتعامل مع الكائنات ، فقط ، طبعا جميع المعاملات نستطيع اعاده تعريفها مثل + ، - ، [] ، << ، ++ ، -- ، الخ .... لنبدأ في البدايه بالمعامل ++ وهي كما هو معروف لزياده واحد . وهذا المعامل يمكن أن يكون prefix أو Postfix أي يكون قبل المتغير (أو الكائن) ، أو بعد المتغير . plain text int x = ++s; // this is prefix , add one first to s and then assign to x int x = s++; // this is postfix , assign s to x first and then add one to s نبدأ بمثال بسيط أولا ، ونرى كيف يمكن تحقيق هذا المثال بدون استخدام مفهوم الoperator overloading ، وبعدها نقوم بتطبيق المفهوم لنرى ماذا يقدمه لنا . ليكن لدينا كلاس اسمه Counter هذا الكلاس يحتوي على متغير x ، ونريد أن نزيد كل مره 1 الى هذا المتغير . شاهد الكود التالي ، وفيه سنستخدم الداله add وهي التي تزيد 1 على قيمه المتغير . expand |plain text // solution without using operator overloading#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int c ) : x(c) { } // this function increment x by 1 void add () { x = x+1; } int getX () { return x; } };int main (){ Counter c(1); // x now is 1 c.add(); // x increment by 1 cout << c.getX() << endl; // print x i.e 2 } الان لدينا الداله Add وهي تؤدي الغرض المطلوب (زياده 1 ) ، ولكن ألقى نظره على المثال التالي : expand |plain text // solution using operator overloading#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int c ) : x(c) { } // this function increment x by 1 void operator++ () { x++; } int getX () { return x; } };int main (){ Counter c(1); // x now is 1 ++c; // x increment by 1 cout << c.getX() << endl; // print x i.e 2 } هنا في هذا المثال استخدمنا مفهوم التحميل الزائد Operator Overlaoding ، ونرى في المثال السابق أن البرنامج أصبح أكثر مقروئيه Readability ، حيث أصبح بمكاننا استخدام ++ مباشره مع الكائن ومن دون أي دوال أخرى . ربما تتسائل الان ، هل الفائده فقط هي هذه المقروئيه ؟؟ بالطبع لا ، ولكن في مثل هذه البرامج البسيطه Toys Example لن تتضح الفائده الكبيره من وراء هذا المفهوم ، ولكنها ستتضح في عندما تحاول تحل مسأله أو مشكله ما ، وسوف نأخذ في نهايه الموضوع بعض الأمثله على هذا . اذاً حاليا بشكل عام عليك أن تعرف كيفيه استخدام هذا المفهوم ، بعدها متى تستخدمه يعود على حسب المسألة التي تحلها . الشكل العام للـ Operator Overlaoding Function هو : plain text returnType Operator op (parameters); op هنا تدل على المعامل الذي نود استخدامه ( ++ ، -- ، + ، * ، ==) (هناك المعامل = وهو يتم تعريفه مباشره عند عملك للكلاس ، أي يقوم الكلاس بتزويده لك مباشره default assignment operator ، سنتكلم عنه بعد قليل ) تبقى شيء واحد في المثال السابق ، وهو أن المثال السابق إعتمدنا مفهوم الزياده القبليه Prefix increment ، فلو قمت بتغيير هذه الزياده من prefix الى postfix ، بدون تغيير الداله الموجوده في الكلاس ، فسوف ينتج خطأ (في حاله استخدمت مترجم حديث مثل gcc ، أو مترجم فيجول سي++ تقريبا اسمه make ) أما اذا استخدمت مترجم قديم فسوف يتنج تحذير warring فقط (مترجم قديم أقصد به Trbuo c++ ، ولا أعلم لماذا الكثير من الجامعات (وخاصه هنا) تحب هذه البيئه مع انه أكل عليها الدهر وشرب ) . لذلك علينا عمل داله للPrefix وداله لل postfix ( طبعا سواء مع الجمع أو الطرح ، الفكره نفسها) . plain text void operator ++ (); // this prefix incrementvoid operator ++ (int); // this postfix increment هنا لاحظ أن في الزياده البعديه postfix نرسل لها متغير ما (أي متغير) ، وفي الحقيقه سي++ تدعم مفهوم عمل داله تستقبل نوع بيانات معين ولكن من غير تحديد اسم له . الان هكذا أصبحنا نفرق بين prefix و postfix ، ولكن ماذا أذا أردنا أن تكون العمليه مشابه لما يحصل مع المتغيرات : plain text int x = ++s; // this is prefix , add one first to s and then assign to x int x = s++; // this is postfix , assign s to x first and then add one to s أي تصبح : plain text Counter x = ++s; // this is prefix , add one first to s and then assign to x Counter x = s++; // this is postfix , assign s to x first and then add one to s فلو غيرنا في المثال السابق الجمله ++c وجعلناها ترجع قيمه بعد الزياده ، فسوف يكون هناك خطأ وهو اننا عرفنا المعامل : plain text void operator ++ (); // this prefix increment بحيث أنه لا يرجع قيمه ... ولذلك علينا بتعديله بحيث يرجع القيمه التي زدناها . وهنا : plain text void operator ++ (int); // this postfix increment نقوم بارجاع القيمه الحاليه للكائن ، بعدها يتم زيادته بواحد . (عن طريق عمل كائن مؤقت نحفظ فيه قيمه الكائن الحالي ، ونجمع واحد للكائن الحالي ، ومن ثم نرجع الكائن المؤقت) . قبل أن ننتقل الى المثال ، علينا بمعرفه كيفيه التعامل مع المؤشر this ، وسوف يكون له الكثير من الأستخدامات في الأمثله القادمه ، أولا علينا أن نعرف أن this هي مؤشر للكائن الحالي (كلام مهم ) . ما المقصود بالكائن الحالي ؟ الكائن الحالي هو الكائن الذي انشأته في الداله main ، وقمت باستدعاء داله ما من الكلاس ، الان في داخل هذه الداله أنا لن أستطيع أن أعرف ما هو الكائن الذي أستدعى هذه الداله ، لذلك اذا اردت أن اشير للكائن الحالي استخدم this ، اي this هي تأخذ نفس عنوان الكائن الذي استدعى الداله ... نأخذ مثال بسيط يبين ذلك : plain text #include <iostream>using namespace std;class Simple{ public : void printAddress () { cout << this << endl; }};int main (){ Simple c; cout << &c << endl; c.printAddress();} الان هنا ، قمنا بعمل كائن من الكلاس Simple ، بعدها قمنا بطباعه عنوان الكائن (باستخدام معامل Addrss of Operator & ) . بعدها تأتي النقطه الأهم ، استدعينا الداله printAddress ، وفيه داخل هذه الداله اردنا أن نطبع عنوان الكائن الحالي ، كيف نطبع العنوان ؟ بالطبع عن طريق this . جرب تنفيذ البرنامج السابق وسترى المخرج هو عباره عن عنون الكائن c . اذا باختصار this هي مؤشر للكائن الحالي . وطبعا جميعنا يعلم اننا اذا اردنا أن نطبع القيمه الموجوده داخل المؤشر يجب أن نستخدم علامه * وتسمى Derefrence ، أي اننا اذا كتبنا *this هنا معناه أننا أردنا قيمه الكائن الحالي . مفهوم ؟ طبعا هناك أستخدام أخر لل this ، هو عندما يكون لدي مثلا متغير اسمه x في الكلاس ، وفي داله البناء لنفس الكلاس ارسلنا متغير اسمه x ، فكيف نفرق بين x الموجوده داخل الكلاس عن الأخرى المرسله ؟ وذلك باستخدام this مع المتغير الموجود داخل الكلاس this->x ; (انظر في داله البناء في المثال القادم ، وهو يشرح هذه الطريقه) . نخرج من this الان ، ونعود الى المثال السابق ، حيث كنا نريد أن المعامل ++ (سواء prefix or postfix) يقوم بارجاع القيمه التي تم زيادتها . قم بتشغيل المثال السابق ، وسوف ترى أن هذه المعاملات أصبحت تعمل كما هو المطلوب تماما : expand |plain text #include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int x) { this->x = x; } // here another usage for this Counter operator++ () { x++; return *this; } Counter operator++ (int) { Counter tmp(*this); x++; return tmp; } int getX() { return x; } };int main (){ Counter c(1); cout << "c = " << c.getX() << endl; c++; cout << "c++ : " << c.getX() << endl; ++c; cout << "++c : " << c.getX() << endl; Counter d = c; // here call to copy Constructor , we will explain it later d = c++; cout << "d = c++ \n"; cout << "c : " << c.getX() << endl; cout << "d : " << d.getX() << endl; d = ++c; cout << "d = ++c \n"; cout << "c : " << c.getX() << endl; cout << "d : " << d.getX() << endl; return 0;} الان البرنامج تمام وما فيه مشكله ، ولكن (وأه من لكن ) في الداله : plain text Counter operator++ () { x++; return *this; } هنا أرجعنا قيمه الكائن الحالي بعد الزياده ، الطريقه صحيحه ، ولكن الإرجاع هنا تم بالقيمه !! كلنا نعلم انه يمكن استقبال متغيرات بالقيمه أو بالمؤشر Pointer أو بالمرجع Reference ، وأيضا يمكن أن نرجع القيمه بأحد هذه الطرق ! المشكله أن الإرجاع بالقيمه (والإستقبال بالقيمه أيضا) مكلف من ناحيه أنه يتم انشاء نسخه جديده من المتغير أو الكائن الذي نريد أن نرجعه أو نستقبله ، وليس كما هو الحال مع الاستقبال أو الارجاع بواسطه المؤشر أو المرجع . كيف يكون تأثير الإرجاع بالقيمه مكلف ؟ ولذلك لأنه يقوم أولا بانشاء نسخه جديده من الكائن الذي نريد استدعائه (يقوم هنا باستدعاء copy constructor ) في كل مره أردنا أن نرجع فيها كائن بالقيمه (ايضا في حاله أستقبلنا كائن بالقيمه) . لذلك الحل الأفضل هو ، اذا لم يكن هناك انشاء لكائن داخل الداله وأردنا أن نرجع قيمه الكائن الحالي فقط ، كما هو الحال مع الداله الأولى فيفضل دائما ارجاع هذه القيمه بواسطه المرجع ويفضل أن تكون ثابته ! (تحدث أخ خالد عن هذه المفاهيم ، من هنا : http://www.arabteam2...howtopic=145287 ). اذا الداله أصبحت بهذا الشكل الأن : plain text const Counter& operator++ () { x++; return *this; } وهنا أرجعنا قيمه الكائن بعد زياده بواسطه المرجع Reference . نعود الأن الى الداله postfix ونرى طرق وسبل تحسينها Optimization : plain text Counter operator++ (int) { Counter tmp(*this); x++; return tmp; } هنا نحن في البدايه كتبناها بشكل محسن ، حيث أننا دائما اذا أنشأنا كائن داخل داله ما ، فيفضل دائما إرجاع قيمه الكائن بواسطه القيمه ، لماذا ؟ حتى لا تحصل مشاكل خروج الكائن من الحياه ونتسبب في وفاته وبعدين يحاكمونا في غواتناماو . (ارجع للرابط السابق لمعرفه السبب) . نأخذ معامل أخر مثلا اشاره الجمع ، أي مثل : plain text Object 1 = Object2 + Object3; أي نجمع قيمه الكائن 3 مع الكائن 2 ونضع الناتج في 1 . هنا داله التحميل الزائد ، سوف تكون بالشكل : plain text Counter operator + (const Counter& rhs) : أي أنها تستقبل القيمه ( وهي هنا تمثل الكائن الذي يأتي بعد عمليه + ) ، وتجمعه مع الكائن الحالي ، وترجع الناتج . (ولأننا سوف ننشيء كائن مؤقت داخل هذه الداله سوف يكون الإرجاع بالقيمه) . والمثال التالي يبين ذلك : expand |plain text #include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int x) { this->x = x; } // here another usage for this Counter operator + (const Counter& rhs) { Counter tmp(*this); tmp.x = tmp.x + rhs.x; return tmp; } int getX() { return x; } };int main (){ Counter c(1); Counter d = c; Counter a(3); cout << "c = " << c.getX() << endl; cout << "d = " << d.getX() << endl; cout << "a = " << a.getX() << endl; c = d + a; cout << "c = " << c.getX() << endl; cout << "d = " << d.getX() << endl; cout << "a = " << a.getX() << endl; return 0;} ويمكن أن تكون الداله بهذا الشكل ، ويعمل البرنامج أيضا : plain text Counter operator + (const Counter& rhs) { return Counter(x + rhs.x); } وهنا عملنا كائن جديد ومرننا فيه قيمه x للكائن الحالي + قيمه x للكائن الأخر ، والسبب في ذلك أن لدينا داله بناء تأخذ عدد ، فذلك العمليه صحيح تبقي مفهوم أو داله Conversion Operator وهي غير ضروريه ، ولكن المغزى منها هو اسناد كائن الى متغير . أي : plain text int x = Object1; وهنا يكون تعريف داله التحميل بهذا الشكل : plain text operator int(); وكل ما عليك هو ارجاع قيمه المتغير (الموجود داخل الكلاس) والذي تريد ان تسنده للمتغير . [color="#FF0000"]نتحدث الأن عن الدوال الصديقه Friend Function أولا الدوال الصديقه Friend Function يعتبرها الكثير أمر غير محبذ الا في بعض الأوقات وللضروه القصوى ، حيث تهز مبدأ الكبسله ، لأنها كما ذكرت لها القابليه لتغيير جميع متغيرات الكلاس سواء عامه public أو خاصه private أو محميه Protected . بالنسبه لاستخدامها فصراحه أشهر استخدام لها هو في اعاده تعريف المعاملات Operator Overloading ، وفي بعض الأحيان يستخدم لزياده المقروئيه Readability ،غير ذلك لم أر له أستخدام من قبل الا في شرح وظيفته فقط ، أما في أمثله كبيره فلم أرى أبد . المهم نأخذ هذا المثال ونرى ماذا تقدم لنا الدوال الصديقه في Operator Overloading ، ولنأخذ كلاس Counter الذي كنا نأخذه قبل قليل .
int x = a+ b;
Object x = ObjectA + ObjectB;
int x = ++s; // this is prefix , add one first to s and then assign to x int x = s++; // this is postfix , assign s to x first and then add one to s
// solution without using operator overloading#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int c ) : x(c) { } // this function increment x by 1 void add () { x = x+1; } int getX () { return x; } };int main (){ Counter c(1); // x now is 1 c.add(); // x increment by 1 cout << c.getX() << endl; // print x i.e 2 }
// solution using operator overloading#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int c ) : x(c) { } // this function increment x by 1 void operator++ () { x++; } int getX () { return x; } };int main (){ Counter c(1); // x now is 1 ++c; // x increment by 1 cout << c.getX() << endl; // print x i.e 2 }
returnType Operator op (parameters);
void operator ++ (); // this prefix incrementvoid operator ++ (int); // this postfix increment
Counter x = ++s; // this is prefix , add one first to s and then assign to x Counter x = s++; // this is postfix , assign s to x first and then add one to s
void operator ++ (); // this prefix increment
void operator ++ (int); // this postfix increment
#include <iostream>using namespace std;class Simple{ public : void printAddress () { cout << this << endl; }};int main (){ Simple c; cout << &c << endl; c.printAddress();}
#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int x) { this->x = x; } // here another usage for this Counter operator++ () { x++; return *this; } Counter operator++ (int) { Counter tmp(*this); x++; return tmp; } int getX() { return x; } };int main (){ Counter c(1); cout << "c = " << c.getX() << endl; c++; cout << "c++ : " << c.getX() << endl; ++c; cout << "++c : " << c.getX() << endl; Counter d = c; // here call to copy Constructor , we will explain it later d = c++; cout << "d = c++ \n"; cout << "c : " << c.getX() << endl; cout << "d : " << d.getX() << endl; d = ++c; cout << "d = ++c \n"; cout << "c : " << c.getX() << endl; cout << "d : " << d.getX() << endl; return 0;}
Counter operator++ () { x++; return *this; }
const Counter& operator++ () { x++; return *this; }
Counter operator++ (int) { Counter tmp(*this); x++; return tmp; }
Object 1 = Object2 + Object3;
Counter operator + (const Counter& rhs) :
#include <iostream>using namespace std;class Counter{ private : int x; public : Counter (int x) { this->x = x; } // here another usage for this Counter operator + (const Counter& rhs) { Counter tmp(*this); tmp.x = tmp.x + rhs.x; return tmp; } int getX() { return x; } };int main (){ Counter c(1); Counter d = c; Counter a(3); cout << "c = " << c.getX() << endl; cout << "d = " << d.getX() << endl; cout << "a = " << a.getX() << endl; c = d + a; cout << "c = " << c.getX() << endl; cout << "d = " << d.getX() << endl; cout << "a = " << a.getX() << endl; return 0;}
Counter operator + (const Counter& rhs) { return Counter(x + rhs.x); }
int x = Object1;
operator int();
#include <iostream>using namespace std;class Counter{private : int x;public : Counter (int u) : x(u) { } int getX() const { return x; } Counter operator + (const Counter& rhs);};Counter Counter :: operator+ (const Counter& rhs){int x = getX() + rhs.getX();Counter tmp(x);return tmp;}int main (){Counter a(1);Counter b(4);Counter c(0);c = a+b; cout << c.getX() << endl;return 0;}
c = a+ 7; cout << c.getX() << endl;
c = 7+a; cout << c.getX() << endl;
#include <iostream>using namespace std;class Counter{private : int x;public : Counter (int u) : x(u) { } int getX() const { return x; } friend Counter operator + ( Counter , Counter);};Counter operator + ( Counter rhs , Counter rhs2){int x = rhs.getX() + rhs2.getX();Counter tmp(x);return tmp;}int main (){Counter a(1);Counter b(4);Counter c(0);c = a+b; cout << c.getX() << endl;c = a+7; cout << c.getX() << endl;c = 3+a; cout << c.getX() << endl;c = 3+4;cout << c.getX() << endl;return 0;}