الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Advanced Function For Bigenners

مغلق
بدأه Wajdy Essam في 9 يناير 2008 · 3 رد · 3,539 مشاهدة · في المواضيع والدروس
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

Operator OverLoading

اعاده تعريف المعاملات أو كما يطلق عليها البعض التحميل الزائد للمعاملات .

كما مر علينا من قبل عند دراستنا للدوال Function وعرفنا أنه يمكن للداله أن نعيد تعريفها (تحميل زائد ) Overload بحيث تستقبل وسائط مختلفه من حيث العدد أو النوع ، فإن هذا المفهوم نطبقه أيضا على المعاملات العاديه + ، - ، * ، < الخ ، حيث نعيد تعريف هذه المعاملات حتى تتعامل مع الكائنات وليس مع أنواع بيانات عاديه Primitive .

لنوضح أكثر قليلا ، عندما نريد أن نجمع متغيرين من نوع int ، نقوم بتطبيق علامه الجمع + بالشكل التالي :

int x = a+ b;

لاحظ هنا جمعنا المتغير a مع المتغير b .

الان في حاله أردنا جمع كائنين ، فاننا لا نستطيع تطبيق (سينتج خطأ عند الترجمه) :

Object x = ObjectA + ObjectB;

لأن المعامل + لا يستطيع معرفه ما الذي يجمعه أو كيف سيجمع كائن مع كائن ؟

لذلك علينا (المبرمج) أن يعيد تعريف المعامل + بحيث يتعامل مع البيانات التي بداخل الكائن ويقوم بجمعها أو طرحها أو العمليه التي نريد .

هذه هي الفكره من موضوع الـ Operator Overloading أي نجعل هذه الأشارات تتعامل مع الكائنات ، فقط ، طبعا جميع المعاملات نستطيع اعاده تعريفها مثل + ، - ، [] ، << ، ++ ، -- ، الخ ....

لنبدأ في البدايه بالمعامل ++ وهي كما هو معروف لزياده واحد . وهذا المعامل يمكن أن يكون prefix أو Postfix أي يكون قبل المتغير (أو الكائن) ، أو بعد المتغير .

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 على قيمه المتغير .

// 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 ) ، ولكن ألقى نظره على المثال التالي :

// 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 هو :

returnType Operator op (parameters);

op هنا تدل على المعامل الذي نود استخدامه ( ++ ، -- ، + ، * ، ==)

(هناك المعامل = وهو يتم تعريفه مباشره عند عملك للكلاس ، أي يقوم الكلاس بتزويده لك مباشره default assignment operator ، سنتكلم عنه بعد قليل )

تبقى شيء واحد في المثال السابق ، وهو أن المثال السابق إعتمدنا مفهوم الزياده القبليه Prefix increment ، فلو قمت بتغيير هذه الزياده من prefix الى postfix ، بدون تغيير الداله الموجوده في الكلاس ، فسوف ينتج خطأ (في حاله استخدمت مترجم حديث مثل gcc ، أو مترجم فيجول سي++ تقريبا اسمه make ) أما اذا استخدمت مترجم قديم فسوف يتنج تحذير warring فقط (مترجم قديم أقصد به Trbuo c++ ، ولا أعلم لماذا الكثير من الجامعات (وخاصه هنا) تحب هذه البيئه مع انه أكل عليها الدهر وشرب ) .

لذلك علينا عمل داله للPrefix وداله لل postfix ( طبعا سواء مع الجمع أو الطرح ، الفكره نفسها) .

void operator ++ (); // this prefix increment
void operator ++ (int); // this postfix increment

هنا لاحظ أن في الزياده البعديه postfix نرسل لها متغير ما (أي متغير) ، وفي الحقيقه سي++ تدعم مفهوم عمل داله تستقبل نوع بيانات معين ولكن من غير تحديد اسم له .

الان هكذا أصبحنا نفرق بين prefix و postfix ، ولكن ماذا أذا أردنا أن تكون العمليه مشابه لما يحصل مع المتغيرات :

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

أي تصبح :

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 وجعلناها ترجع قيمه بعد الزياده ، فسوف يكون هناك خطأ وهو اننا عرفنا المعامل :

void operator ++ (); // this prefix increment

بحيث أنه لا يرجع قيمه ... ولذلك علينا بتعديله بحيث يرجع القيمه التي زدناها .

وهنا :

void operator ++ (int); // this postfix increment

نقوم بارجاع القيمه الحاليه للكائن ، بعدها يتم زيادته بواحد .

(عن طريق عمل كائن مؤقت نحفظ فيه قيمه الكائن الحالي ، ونجمع واحد للكائن الحالي ، ومن ثم نرجع الكائن المؤقت) .

قبل أن ننتقل الى المثال ، علينا بمعرفه كيفيه التعامل مع المؤشر this ، وسوف يكون له الكثير من الأستخدامات في الأمثله القادمه ،

أولا علينا أن نعرف أن this هي مؤشر للكائن الحالي (كلام مهم ) .

ما المقصود بالكائن الحالي ؟

الكائن الحالي هو الكائن الذي انشأته في الداله main ، وقمت باستدعاء داله ما من الكلاس ، الان في داخل هذه الداله أنا لن أستطيع أن أعرف ما هو الكائن الذي أستدعى هذه الداله ، لذلك اذا اردت أن اشير للكائن الحالي استخدم this ،

اي this هي تأخذ نفس عنوان الكائن الذي استدعى الداله ... نأخذ مثال بسيط يبين ذلك :

#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) يقوم بارجاع القيمه التي تم زيادتها .

قم بتشغيل المثال السابق ، وسوف ترى أن هذه المعاملات أصبحت تعمل كما هو المطلوب تماما :

#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;
  }

هنا أرجعنا قيمه الكائن الحالي بعد الزياده ، الطريقه صحيحه ، ولكن الإرجاع هنا تم بالقيمه !! كلنا نعلم انه يمكن استقبال متغيرات بالقيمه أو بالمؤشر Pointer أو بالمرجع Reference ، وأيضا يمكن أن نرجع القيمه بأحد هذه الطرق !

المشكله أن الإرجاع بالقيمه (والإستقبال بالقيمه أيضا) مكلف من ناحيه أنه يتم انشاء نسخه جديده من المتغير أو الكائن الذي نريد أن نرجعه أو نستقبله ، وليس كما هو الحال مع الاستقبال أو الارجاع بواسطه المؤشر أو المرجع .

كيف يكون تأثير الإرجاع بالقيمه مكلف ؟ ولذلك لأنه يقوم أولا بانشاء نسخه جديده من الكائن الذي نريد استدعائه (يقوم هنا باستدعاء copy constructor ) في كل مره أردنا أن نرجع فيها كائن بالقيمه (ايضا في حاله أستقبلنا كائن بالقيمه) .

لذلك الحل الأفضل هو ، اذا لم يكن هناك انشاء لكائن داخل الداله وأردنا أن نرجع قيمه الكائن الحالي فقط ، كما هو الحال مع الداله الأولى فيفضل دائما ارجاع هذه القيمه بواسطه المرجع ويفضل أن تكون ثابته !

(تحدث أخ خالد عن هذه المفاهيم ، من هنا :

/index.php?showtopic=145287

).

اذا الداله أصبحت بهذا الشكل الأن :

const Counter& operator++ () 
  {
   x++;
   return *this;
  }

وهنا أرجعنا قيمه الكائن بعد زياده بواسطه المرجع Reference .

نعود الأن الى الداله postfix ونرى طرق وسبل تحسينها Optimization :

 Counter operator++ (int)
  {
   Counter tmp(*this);
   x++;
   return tmp;
  }

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

(ارجع للرابط السابق لمعرفه السبب) .

نأخذ معامل أخر مثلا اشاره الجمع ، أي مثل :

Object 1 = Object2 + Object3;

أي نجمع قيمه الكائن 3 مع الكائن 2 ونضع الناتج في 1 .

هنا داله التحميل الزائد ، سوف تكون بالشكل :

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);
  }

وهنا عملنا كائن جديد ومرننا فيه قيمه x للكائن الحالي + قيمه x للكائن الأخر ، والسبب في ذلك أن لدينا داله بناء تأخذ عدد ، فذلك العمليه صحيح

تبقي مفهوم أو داله Conversion Operator وهي غير ضروريه ، ولكن المغزى منها هو اسناد كائن الى متغير . أي :

int x = Object1;

وهنا يكون تعريف داله التحميل بهذا الشكل :

operator int();

وكل ما عليك هو ارجاع قيمه المتغير (الموجود داخل الكلاس) والذي تريد ان تسنده للمتغير .

نتحدث الأن عن الدوال الصديقه Friend Function

أولا الدوال الصديقه Friend Function يعتبرها الكثير أمر غير محبذ الا في بعض الأوقات وللضروه القصوى ، حيث تهز مبدأ الكبسله ، لأنها كما ذكرت لها القابليه لتغيير جميع متغيرات الكلاس سواء عامه public أو خاصه private أو محميه Protected .

بالنسبه لاستخدامها فصراحه أشهر استخدام لها هو في اعاده تعريف المعاملات Operator Overloading ، وفي بعض الأحيان يستخدم لزياده المقروئيه Readability ،غير ذلك لم أر له أستخدام من قبل الا في شرح وظيفته فقط ، أما في أمثله كبيره فلم أرى أبد .

المهم نأخذ هذا المثال ونرى ماذا تقدم لنا الدوال الصديقه في Operator Overloading ، ولنأخذ كلاس Counter الذي كنا نأخذه قبل قليل .

#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;
}

الان هنا جمع 1+4 والقيمه الناتجه هنا 5 ، هنا في هذه العمليه قمنا بجمع الكائن a مع الكائن b ولأننا لدينا داله قمنا باعاده تعريفها لجمع المتغير الذي يكون بداخل الكائن فان العمليه صحيحه .

الأن في حاله قمنا بجمع كائن مع متغير ، فان العمليه أيضا صحيحه ، جرب واكتب :

										c = a+ 7;  
cout << c.getX() << endl;

في الداله main ، وشاهد التنفيذ والمخرج يكون هو 8 .

كيف عمل الكود السابق ، أولا بما أن هناك عمليه جمع مع الكائن ، اذا فسوف يذهب المترجم ليرى هل هناك داله + قمنا باعاده تعريفها ، فاذا كانت لا توجد فسوف يطبع خطأ ، المهم في حالتنا الداله موجوده ، وسوف يرسل ما بعد علامه + الى الداله كوسيط ، المهم هنا انه الوسيط الذي ارسلناه هو رقم وليس كائن ، لكن العمليه تعتبر صحيحه وذلك لأن هناك داله بناء تأخذ عدد صحيح ، أي كأن العدد 7 ارسل الى كائن جديد ، وتم استدعاء داله البناء الخاصه بهذا الكائن .

حسنا ، الى هنا الأمر جميل ، ولكن ماذا اذا أردنا أن نكتب العكس أي :

										c = 7+a;  
cout << c.getX() << endl;

هنا سوف يصرخ المترجم ويعلن عن خطأ ، لأن 7 ليست كائن ، اذا علامه الجمع سوف يتعبرها علامه عاديه ، بعدها سوف يذهب للوسيط الثاني ليرى أنه كائن ، ولن يستطيع جمع عدد مع كائن .

الحل هنا عمل داله صديقه ( والدوال الصديقه دائما تأخذ وسيط يمثل الكلاس الذي تريد أن تصل الى متغيراته) ، وبما أنه هنا لدينا كائنين ، فاذا الداله الصديقه سوف تأخذ كائنين ، وتقوم بعمليه الجمع ، سواء الكائن الأول كان رقم أم كائن ، فسوف تتم عمليه الجمع .

وها هو المثال بعد استخدام freind Function :

#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;
}

وسوف تلاحظ هنا اختلاف طريقه كتابه الداله + عندما تكون موجوده داخل الكلاس Member Function ، وعندما تكون داله صديقه Friend Fucntion (معرفه بالخارج حيث جميع الدوال الصديقه تعرف بالخارج) .

قد لا تبدوا الداله الصديقه منطقيه ، حيث ربما تتسائل لماذا أخذت وسيطين ، لكن عليك باعتبارها داله عاديه اسمها + ، اذا أردت استخدامها أرسل الوسيط الأول ومن ثم + ومن ثم الوسيط الثاني . فقط .

الأستخدام الأخر له وهو يكون في هذه الحاله أكثر وضوحا من الدوال Member Function .

مثلا نأخذ نفس الكلاس Counter وليكن فيه داله تربيع Square ترجع قيمه x*x :

كود

تم تعديل هذه المشاركة بواسطة romansy في 9 يناير 2008 في 21:23

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

#2

ماشاء الله عليك اخي

بارك الله فيك

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#3

السلام عليكم ,,

ما شاء الله أخي العزيز, متميز كعادتك B)

:stop:

من الآن وصاعداً يمنع وضع أي سؤال حول موضوع الـ operators overloading, و لكن يتم توجيه السائل إلى الدرس و طرح الأسئلة على الدرس نفسه,

أخ رومانسي, لاحظ أن هذا الموضوع ليس بالهين, و أنت ماشاء الله وضعت مرجعاً شبه متكامل للموضوع, رغم أن الكثير من الكتب لا تفصل في الموضوع كما فعلت أنت :)

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

حقيقة لدي عدة تعليقات على الموضوع,

أولاً أنت ذكرت في بداية الكلام أن الغرض ليس readability فقط, و كلامك صحيح مئة بالمئة, لأن المعامل يمكن أن يرجع الكائنات كما تفضلت و بالتالي نحصل على ما يسمى بالـ chaining,

ثانياً موضوع الـ conversion operators يعتبر من المواضيع التي لا يعرف عنها الكثيرون في عالم الـ ++C, و هذه تمكننا ليس فقط من إسناد الكائن إلى متغير بل يمكن تعريف operators لإسناد كائن إلى كائن آخر!

ثالثاً, هناك معاملات كثيرة يمكن إعادة تعريفها, و إحداها المعامل new !! يمكن إعادة تعريفه لتقوم بحجز الذاكرة لفئتك على كيفك :Dهذا غير موجود في اللغات الأخرى بأي شكل,

بل إن كثير من اللغات لاتوفر إعادة تعريف المعاملات في أبسط أشكالها :blink: و لاداعي لذكر الأسماء :D

تحياتي ,,

#4

السلام عليكم ,,

يبدو أن الأخوة لم يعرفوا كيفية الاستفادة من هذا الدرس,

و لكن بكل بساطة إذا كنت تعلمت مبادئ البرمجة الكائنية, فإنه ينقصك الكثير الكثير من التفاصيل , إذا قمت بدراسة هذا الموضوع على مدى يومين على الاقل,

ستصبح بإذن الله متمكناً من برمجة الكائنات في ++C,

أشكر الأخ romansy, ليس لأنه وفر لنا هذه المادة العلمية فحسب, بل لأنه صرف الكثير من جهده لإعداده بنفسه,

هذا النوع من الدروس لن تجده إلا في الكتب التي تكلفك $$$$$, و لن تجده بهذا التفصيل لأن أخينا عرض الكثير من التطبيقات في هذا الدرس,

يمكنك الذهاب إلى مواقع كـ codeproject أو codeguru و ستجد أن كل فقرة من هذا الدرس وضعت في موضوع منفصل على الأقل,,

نصيحتي لأي طالب أو مبرمج أن يقرأ الدرس و يستوعبه, لأنه بتعلمه للفئات في ++C فهو قد أقدم على أول الطريق فقط, و هذا الدرس مهد معظم الباقي ليصبح مبرمج متوسط على الأقل,

أنا في انتظار أسئلتكم لأخينا romansy بعد قرائتكم للدرس,,

تحياتي ,,

هذا الموضوع مغلق.

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…