اذهب إلى المحتوى

لا شك أن اختيار لغة البرمجة المناسبة لتطبيقاتك وبرامجك أمرٌ صعب، لاسيما عندما لا تملك خبرة عميقة بالخيارات المتاحة، وفي مقال اليوم نعقد لك مقارنة شاملة بين لغتي C++‎‎ وجافا ونستكشف أبرز الاختلافات الأساسية بينهما، وما هي أبرز العوامل التي عليك أخذها بعين الاعتبار عند اختيارك لإحديهما.

هناك الكثير من المقالات التي تقارن بين المميزات التقنية للغتي C++‎‎ وجافا، ولكنها لا توضح لك بشكل دقيق ما هي الجوانب التي ستؤثر بها هذه الاختلافات، على سبيل المثال، لا تدعم لغة جافا الوراثة المتعددة بينما تدعمها لغة C++‎، حسنًا ماذا يعني ذلك لي كمطور؟ وهل هذا أمر جيد أم لا؟ لماذا يعتبر البعض بأن هذه ميزة في جافا، بينما يعتبرها آخرون مشكلة.

دعنا نستكشف كافة الحالات التي يجب على المطورين فيها اختيار لغة C++‎‎ أو جافا أو ربما لغة أخرى في تطوير التطبيقات، ولماذا يعد اختيار اللغة الصحيحة أمرًا مهمًا لنجاح مشروعك البرمجي.

الفروقات الأساسية بين C++‎‎ وجافا: بناء اللغة والنظام البيئي

أُُطلِقت لغة C++‎‎ عام 1985 كواجهة أمامية للغة سي C فهي تستخدم الكثير من مفاهيم وبنية لغة C، ولكنها أيضًا تضيف ميزات جديدة وتحسينات عليها، وهذا يشبه إلى حد ما طريقة ترجمة لغة TypeScript إلى جافا سكريبت JavaScript . عادةً ما تقوم مترجمات C++‎‎ الحديثة بترجمة أكواد C++‎‎ إلى لغة الآلة الأصلية، وعلى الرغم من أن البعض يرى أن مترجمات C++‎‎ تقلل من قابلية التنقل بين المنصات، وتتطلب إعادة بناء البرامج للتكيف مع المعماريات الحديثة للحواسيب، إلا أن كود C++‎‎ يعمل على جميع منصات المعالجات تقريبًا.

أما لغة جافا فقد صدرت لأول مرة في عام 1995، وهي تتميز بأنها لا تترجم مباشرةً إلى لغة الآلة الأصلية Native Machine Code، بل تترجم بدلاً من ذلك إلى لغة وسيطة تسمى شيفرة البايت bytecode، وهي لغة ذات تمثيل ثنائي مخصصة للعمل على آلة جافا الافتراضية JVM أي بعبارة أخرى، يحتاج مترجم جافا الذي يترجم كود لغة جافا إلى كود يفهمه الحاسوب إلى ملف تشغيلي أصيل خاص بكل منصة أو نظام التشغيل كي يعمل بشكل صحيح. لذا، تختلف جافا عن بعض لغات البرمجة الأخرى من حيث أن كودها المصدري لا يتحول مباشرة إلى برنامج قابل للتشغيل بل يحول إلى كود البايت أولًا والذي يحتاج تنفيذه لبرنامج آخر يسمى آلة جافا الافتراضية.

وتصنف كل من لغة C++‎‎ و جافا بأنها من عائلة اللغات الشبيهة بلغة سي C، حيث أنهما تشبهان لغة C بشكل عام في تراكيبها اللغوية وبنية التعليمات البرمجية، لكن يكمن الفرق الأبرز بينما في البيئة التشغيليلة الخاصة بكل لغة، حيث يمكن للغة C++‎‎ استدعاء المكتبات المستندة إلى C أو ++C، أو استدعاء واجهة برمجة التطبيقات API لنظام التشغيل بسهولة كبيرة، أما لغة جافا فهي ملاءمةً بشكل أفضل للمكتبات المعتمدة على لغة جافا، ويمكن الوصول إلى مكتبات C في جافا باستخدام واجهة برمجة التطبيقاتJava Native Interface أو اختصارًا JNI، لكن هذا قد يتسبب في وقوع بعض الأخطاء ويتطلب كتابة بعض أكواد C أو ++C، كما تتفاعل لغة C++‎‎ مع الأجهزة بسهولة أكبر من لغة جافا لكونها أقرب للعتاد وتعد أقرب إلى لغات البرمجة منخفضة المستوى.

الفروقات التفصيلية بين لغة C++‎‎ وجافا: الأنواع المعمعة وإدارة الذاكرة وغيرها

يمكننا مقارنة لغة C++‎‎ بلغة جافا من جوانب عديدة، ففي بعض الحالات يكون قرار اختيار لغة C++‎‎ أو لغة جافا واضحًا وسهلًا، على سبيل المثال في حال أراد مطور ما برمجة تطبيقات أصيلة لنظام التشغيل أندرويد ففي الغالب سيختار لغة جافا لهذه المهمة، إلا في حال كان التطبيق المطلوب عبارة عن لعبة فيديو، ففي هذه الحالة سيختار لغة C++‎‎ أو لغة أخرى من لغات برمجة الألعاب تساعده على الحصول على رسوم متحركة أكثر انسيابيةً في الزمن الحقيقي، فغالبًا ما تسبب إدارة الذاكرة في جافا في حدوث تأخير غير محبب أثناء اللعب. أما بالنسبة لتطوير التطبيقات متعددة المنصات التي ليست ألعابًا فهي خارج نطاق المناقشة اليوم، إذ لا تعد لا لغة C++‎‎ ولا لغة جافا مثاليتين في هذه الحالة لأنهما لغتان تحتاجان لكتابة تعليمات برمجية طويلة جدًا لتطوير واجهة المستخدم الرسومية، ولا تساعد المطورين على برمجتها بكفاءة، وبالنسبة إلى التطبيقات عالية الأداء من الأفضل إنشاء وحدات C++‎‎ للقيام بالأعمال الثقيلة، واستخدام لغة أكثر إنتاجية لتطوير الواجهة الأمامية لهذه التطبيقات.

وبما أن اختيار اللغة الأنسب قد لا يكون واضحًا بالنسبة لبعض المشاريع، لذلك دعونا نقارن بين اللغتين بتفصيل أكبر من خلال الجدول التالي:

الخاصية C++‎‎ جافا
سهولة الاستخدام للمبتدئين لا نعم
أداء زمن التشغيل ممتاز جيد
التأخير Latency قابل للتنبؤ غير قابل للتنبؤ
المؤشرات الذكية للعد المرجعي Reference-counting smart pointers نعم لا
أسلوب جمع وتحرير القمامة أو البيانات المهملة في الذاكرة لا مطلوب
تخصيص ذاكرة المكدس Stack memory allocation نعم لا
الترجمة إلى ملف أصيل قابل للتنفيذ نعم لا
الترجمة إلى شيفرة البايت bytecode جافا Compilation to Java bytecode لا
التفاعل المباشر مع واجهات برمجة تطبيقات نظام التشغيل منخفضة المستوى نعم يتطلب كود C
التفاعل المباشر مع مكتبات C نعم يتطلب كود C
التفاعل المباشر مع مكتبات جافا من خلال JNI نعم
البناء وإدارة الحزم بشكل موحد لا Maven

إضافة إلى الميزات التي قارنها في الجدول أعلاه، سنركز أيضًا على ميزات أخرى مثل البرمجة كائنية التوجه OOP مثل الوراثة المتعددة multiple inheritance، والأنواع المعممة generics، والقوالب templates، والانعكاس reflection. فكلا اللغتين مثلًا تدعمان البرمجة كائنية التوجه مع فرق بسيط بينهما حيث تفرض لغة جافا استخدام نموذج البرمجة كائنية التوجه، بينما تدعم لغة C++‎‎ البرمجة كائنية التوجه إلى جانب نماذج برمجة أخرى.

الوراثة المتعددة

الوراثة هي أحد مبادئ البرمجة كائنية التوجه وهي تمكن صنف ابن child class من وراثة الواصفات attributes والتوابع methods من صنف أب parent class. وأحد الأمثلة القياسية على مبدأ الوراثة هو صنف المستطيل Rectangle الذي يرث من صنف الشكل Shape الأكثر عمومية:

// Note that we are in a C++‎‎ file

class Shape {

  // Position

  int x, y;

 public:

  // The child class must override this pure virtual function

  virtual void draw() = 0;

};



class Rectangle: public Shape {

  // Width and height

  int w, h;

 public:

  void draw();

};

تحدث الوراثة المتعددة عندما يرث صنف ابن من عدة أصناف آباء بذات الوقت. فيما يلي مثال يستخدم صنف المستطيل Rectangle وصنف الشكل Shape وصنف إضافي Clickable:

// Not recommended

class Shape {...};

class Rectangle: public Shape {...};

class Clickable {

  int xClick, yClick;

 public:

  virtual void click() = 0;

};


class ClickableRectangle: public Rectangle, public Clickable {

  void click();

};

لدينا في هذه الحالة صنفان أساسيان هما صنف الشكل Shape (الذي يمثل الصنف الأساسي أو الصنف الأب للمستطيل Rectangle) والصنف الأساسي Clickable والصنف ClickableRectangle الذي يرث من كل من الصنفين السابقين ويملك خصائص نوعي الكائنات التي يولدها كل صنف. تدعم لغة C++‎‎ الوراثة المتعددة، بينما لا تدعم لغة جافا هذه الميزة، وتعد ميزة الوراثة المتعددة مفيدة في بعض حالات المحددة مثل:

  • إنشاء لغة تخصصية Domain-Specific Language أواختصارًا DSL للعمل في مجال معين ولحل مشكلة مخصصة.
  • إجراء حسابات معقدة في وقت تصريف البرنامج كما في تطبيقات الزمن الحقيقي.
  • تحسين سلامة المشروع البرمجي وضمان أمنه بطرق لا يمكن تحقيقها في لغة جافا.

ومع ذلك، يُنصح عمومًا بعدم استخدام الوراثة المتعددة إلا عند الضرورة لكونها قد تعقد الكود البرمجي.

الأنواع المعممة والقوالب

تعمل الإصدارات المعممة Generics للأصناف مع أي نوع من أنواع البيانات وهي تسهل إعادة استخدام الكود البرمجي، وتدعم لغة جافا هذه الميزة عن طريق الأنواع المعممة في حين تدعم لغة C++‎‎ هذه الميزة عن طريق القوالب templates، ولكن مرونة التعامل مع قوالب C++‎‎ تجعل عملية البرمجة أكثر أمنًا وقوة. حيث يمكن لمترجمات C++‎‎ إنشاء أصناف أو وظائف مخصصة جديدة في كل مرة تستخدم فيها أنواعًا مختلفة مع القالب، كما يمكن لقوالب C++‎‎ استدعاء وظائف أو دوال مخصصة بناءً على أنواع وسطاء الدالة، مما يسمح بتخصيص شيفرات مخصصة لأنواع بيانات معينة بدلًا من إنشاء إصدار واحد يناسب جميع أنواع البيانات، يطلق على هذه الميزة اسم تخصيص القالب template specialization.

لا تملك لغة جافا ميزة مكافئة لهذه الميزة. بل على العكس تمامًا عند استخدام الأنواع المعممة generics في لغة جافا، تنشئ مترجمات لغة جافا كائنات عامة دون أنواع محددة من خلال عملية تسمى شطب النوع type erasure. وتجري عملية التحقق من النوع أثناء عملية الترجمة، ولكن لا يمكن للمبرمجين تعديل سلوك صنف أو تابع عام بناءً على نوع وسطائه.

وكي تتمكن من فهم هذا الأمر بشكل أفضل، انظر سريعًا على الدالة العامة التالية format:
std::string format(std::string fmt, T1 item1, T2 item2)
تستخدم هذه الدالة قالب template<class T1, class T2>‎‎ من مكتبة C++‎‎ تم إنشاؤها:

std::string firstParameter = "A string";
int secondParameter = 123;
// Format printed output as an eight-character-wide string and a hexadecimal value
format("%8s %x", firstParameter, secondParameter);
// Format printed output as two eight-character-wide strings
format("%8s %8s", firstParameter, secondParameter);

تنشأ الدالة العامة format في لغة C++‎‎ على النحو التالي (لاحظ أننا حددنا نوع وسطاء الدالة ليكون نص string ثم عدد صحيح int)

std::string format(std::string fmt, std::string item1, int item2)

بينما تنشأ هذه الدالة في لغة جافا دون تحديد أنواع الكائنات string و int الخاصة بوسطاء الدالةitem1 و item2 وفي هذه الحالة، يعرف القالب في لغة C++‎‎ أن آخر معامل مرسل هنا هو عدد صحيح int، وبالتالي يمكنه إجراء عملية تحويله لنص std::to_string المطلوب في الاستدعاء الثاني للدالة format.

وبدون استخدام القوالب، قد تؤدي عبارة printf في لغة C++‎‎ والتي تحاول طباعة رقم على هيئة سلسلة نصية كما في الاستدعاء الثاني للدالة formatفي الكود السابق إلى سلوك غير محدد وقد تتسبب في تعطل التطبيق أو طباعة قيم غير صحيحة. من ناحية أخرى،ستكون الدالة في جافا قادرة على التعامل مع الرقم كنص في الاستدعاء الأول، ولكن لن تنسقه على هيئة قيمة ست عشرية مباشرةً.

وعلى الرغم من بساطة هذا المثال، لكنه يوضح قدرة لغة C++‎‎ على تعريف واختيار قالب مخصص للتعامل مع أي كائن من الصنف بشكل ديناميكي دون الحاجة لتعديل الصنف أو تعديل كود الدالة format، ويمكنك الحصول على نفس النتيجة في لغة جافا باستخدام ميزة تسمى الانعكاس reflection كبديل على استخدام الأنواع المعممة generics، ولكن هذه الطريقة أقل مرونة وأكثر عرضة للأخطاء.

الانعكاس Reflection

تمكنك لغة البرمجة جافا من معرفة واستكشاف كافة تفاصيل بنية الكائن (في وقت التشغيل)، حيث يمكنك معرفة ما هي الأعضاء المتاحة في صنف أو نوع صنف معين. وتُسمى هذه الميزة الانعكاس Reflection لكونها تشبه رفع مرآة أمام الكائن لرؤية كل ما يوجد بداخله.

في حين لا تمتلك لغة C++‎ ميزة الانعكاس بالكامل، ولكن تمكنك إصدارات C++‎‎ الحديثة من الحصول على معلومات عن نوع الصنف واكتشاف نوع صنف محدد في وقت تنفيذ الكود، لكنها غير قادرة على الوصول إلى معلومات أخرى مثل معلومات حول خصائص الصنف. ‎‎‎

إدارة الذاكرة

تختلف لغتا جافا C++‎ كذلك في الأسلوب المتبع في إدارة الذاكرة والتي تتبع عادة أحد طريقتين رئيسيتين الأولى هي الطريقة اليدوية التي يتوجب فيها على المطورين تتبع الذاكرة وتحريرها يدويًا، والطريقة الثانية هي الطريقة الآلية ، حيث يقوم البرنامج بتتبع ومراقبة الكائنات التي لا تزال قيد الاستخدام في الذاكرة، فإذا لم يعد أي كائن قيد الاستخدام، سيعتبر قمامة ويمكن في هذه الحالة إعادة استخدام الذاكرة التي كان يحجزه ويستخدمها، ويسمى هذا الأسلوب في إدارة الذاكرة جمع القمامة أو كنس البيانات المهملة garbage collection.

تستخدم لغة جافا هذا الأسلوب الذي يمكنها من إدارة الذاكرة بسهولة أكبر من الطريقة اليدوية، كما أنه يساعد في منع وقوع أخطاء تحرير الذاكرة التي تسهم غالبًا في حدوث ثغرات أمنية . من ناحية أخرى لا توفر لغة C++‎‎ طريقة إدارة الذاكرة الآلية بشكل مدمج لكنها تدعم نوعًا من أنواع كنس البيانات يطلق عليه اسم المؤشرات الذكية smart pointers حيث تستخدم المؤشرات الذكية أسلوبًا يسمى عدّ المراجع وهي طريقة آمنة وفعالة في حال استخدامها بشكل صحيح، كما تقدم لغة C++‎‎ أيضًا مفهوم الهودام destructors التي تقوم بتنظيف أو تحرير الموارد المخصصة للكائن عن حذفه من الذاكرة.

وتقدم لغة جافا تخصيص الذاكرة بالطريقة الآلية وفق أسلوب يسمى تخصيص الذاكرة المعتمد على الكومة heap allocation (باستخدام الدالة new أو delete أو الدالة malloc الأقدم التي أتت من لغة C) وتخصيص الذاكرة المعتمد على المكدس stack allocation. ويمكن القول بأن تخصيص الذاكرة المعتمد على المكدس أسرع وأكثر أمانًا من تخصيص الذاكرة المعتمد على الكومة، لأن المكدس هو بنية بيانات خطية في حين أن الكومة هو بينة بيانات شجرية، لذا فإن أسلوب ذاكرة المكدس أسهل بكثير من ناحية التعامل مع تخصيص وتحرير الذاكرة.

وهناك أيضًا ميزة أخرى لصالح لغة C++‎‎ تتعلق بتخصيص الذاكرة بالاعتماد على المكدس وهي استخدام تقنية برمجية معروفة باسم Resource Acquisition Is Initialization أو اختصارًا RAII أي أن الحصول على المورد يجب أن يحدث عند إنشاء وتهيئة الكائن، وتربط الموارد مثل المراجع references بدورة حياة الكائن الذي يتحكم فيها، وتحذف أو تحرر في نهاية دورة حياة هذا الكائن.

يُحرر مرجع المؤشر الذكي المشار إليه في أعلى الدالة تلقائيًا عند الخروج من الدالة. كما تُحرر الذاكرة المرتبطة به إذا كان هذا هو المرجع الأخير إلى المؤشر الذكي. وبهذه الطريقة تعمل المؤشرات الذكية في C++‎‎ دون الحاجة إلى تحرير المراجع يدويًا، وعلى الرغم من أن لغة جافا تتبع طريقة مشابهًا لهذا الأسلوب في تحرير الذاكرة، إلا أن الأمور تنجز بطريقة أصعب من تقنية تخصيص المورد عند إنشاء الكائن RAII المستخدمة في لغة C++‎‎ خاصةً إذا كنت بحاجة إلى إنشاء عدة موارد في نفس كتلة التعليمات البرمجية.

الأداء في وقت التشغيل

يدل هذا المفهوم على مدى سرعة تشغيل البرنامج وفي هذا الصدد تملك لغة جافا أداء تشغيل جيد، ولكن تظل لغة C++‎‎ أعلى منها في الأداء لأن إدارة الذاكرة اليدوية أسرع من تقنية جمع القمامة. وبالرغم من ذلك يمكن أن تتفوق لغة جافا في الأداء على C++‎‎ وذلك في حالات خاصة معينة بسبب ميزة الترجمة اللحظية للغة Just-In-Time أو اختصارًا JIT

وبشكل مخصص تُشغّل مكتبة الذاكرة القياسية في جافا جمع القمامة بشكل كبير، مقارنة بالتخصيص الأقل لذاكرة الكومة Heap في ++C لكن تبقى لغة جافا لغة سريعة نسبيًا ومقبولة الأداء في التطبيقات التي لا تكون فيها سرعة الاستجابة أمرًا أساسيًا مثل الألعاب أو التطبيقات التي تتطلب استجابة في الزمن الحقيقي.

بناء وإدارة الحزم

يمكن التغاضي عن نقص أداء لغة جافا مقابل سهولة الاستخدام التي توفرها لا سيما في إدارة الحزم وطريقة بناء التطبيقات وإضافة التبعيات الخارجية إليها. حيث توفر لغة جافا أداة تسمى Maven تبسط العمل على المشاريع وتمكنك من إداراتها ببضع خطوات سهلة كما أنها تتكامل مع العديد من بيئات التطوير المتكاملة مثل IntelliJ IDEA.

أما في لغة ++C فلا يوجد مستودع حزم قياسي ولا أسلوب موحد لبناء كود تطبيقات C++‎‎ فبعض المطورين يفضلون استخدام محرر فيجوال استوديو Visual Studio، بينما يستخدم آخرون أداة CMake أو مجموعة أدوات مخصصة أخرى، كما أن بعض مكتبات C++‎‎ التجارية المتنوعة غير مفتوحة المصدر تكون متوفرة بتنسيق ثنائي، ولا يوجد أسلوب ثابت لدمج تلك المكتبات في التطبيقات، وقد تتسبب التغييرات في إعدادات البناء أو إصدارات المُصرّف مشكلات وأخطاء تحول دون عمل هذه المكتبات الثنائية بشكل صحيح.

ملاءمة اللغة للمبتدئين

تعد لغة C++‎‎ صعبة الاستخدام وغير ملائمة للمبتدئين ليس فقط بسبب تعقيد بناء التطبيقات باستخدمها وصعوبة إدارة الحزم البرمجية، بل بسبب عوامل أخرى من بينها صعوبة تصحيح الأخطاء البرمجية، وصعوبة استخدام اللغة بطريقة آمنة ما لم تكن على دراية جيدة بلغة C ولغات التجميع وبطريقة عمل أجزاء الحاسوب منخفضة المستوى، فلغة C++‎‎ أداة قوية يمكن أن تنجز من خلالها الكثير، ولكنها لغة خطيرة إذا استخدمتها بطريقة خاطئة.

من ناحية أخرى، تعد لغة جافا أسهل للمبتدئين مقارنةً بلغة C++‎‎ بسبب أسلوب إدارة الذاكرة السهل الذي تتبعه والذي شرحناه سابقًا، فلن يحتاج مطورو جافا للقلق بشأن تحرير ذاكرة الكائنات غير المستخدمة حيث يمكن للغة التعامل مع هذه المهمة تلقائيًا بكل سهولة.

هل أختار C++‎‎ أم Java؟

الآن بعد تعرفت على أهم الاختلافات والفروقات بين لغة C++‎‎ و Java لنعد للسؤال الأساسي هل أستخدم C++‎‎ أم جافا في تطوير تطبيقاتي؟ في الواقع لا يوجد جواب موحد على هذا السؤال يناسب الجميع حتى مع الفهم العميق لميزات كل من هاتين اللغتين.

فقد يفضل مهندسو البرمجيات الذين لا يعرفون مفاهيم البرمجة على مستوى منخفض لغة البرمجة جافا عندما لا يكون أمامهم سوى خيارين فقط هما جافا أو C++‎، إلا في حال الحاجة لتطوير تطبيقات زمن حقيقي مثل الألعاب كما شرحنا من قبل. من ناحية أخرى، قد يختار المطورون الذين يسعون إلى تطوير خبراتهم البرمجية
لغة C++‎.

ورغم ذلك قد تؤثر الفروق التقنية بين لغتي C++‎‎ و Java بشكل بسيط على قرار اختيار اللغة الأنسب، ويعتمد نوع اللغة بشكل أكبر على طبيعة التطبيقات أو المنتجات التي تريد تطويرها فقد تكون جافا هي الأنسب أو هي الأنسب أو ربما في النهاية برمجة ثالثة هي الأنسب.

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

دليل شامل لاختيار أفضل لغة لمختلف أنواع المشاريع

الخلاصة

تعرفنا في هذا المقال على أبرز الفروقات بين لغة C++‎‎ وجافا من عدة جوانب تتعلق بالإنتاجية وإدارة الذاكرة وطريقة عمل مصرّفات اللغة وغيرها من العوامل، ويمكن القول ختامًا أن لغة C++‎‎ تتفوق في الأداء على لغة جافا وتوفر إمكانية الوصول إلى ميزات منخفضة المستوى التي تفتقر إليها لغة جافا، في حين تتميز جافا بسهولة الاستخدام وتعتبر خيارًا أفضل للمبرمجين المبتدئين. وبالرغم من أن C++‎‎ و Java تنتميان إلى عائلة لغات سي C، إلا أنهما يحملان تصميمًا مستقلاً، ويبقى العامل الأهم في اختيار اللغة المناسبة هو أهداف تطبيقك وطبيعة المهام المطلوبة فيه.

ترجمة بتصرف لمقال An In-depth Look at C++ vs. Java للكاتب Timothy Mensch

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...