نظرة متعمقة على C ++ مقابل Java

نشرت: 2022-07-22

عدد لا يحصى من المقالات تقارن الميزات التقنية لـ C ++ و Java ، ولكن ما هي الاختلافات الأكثر أهمية التي يجب مراعاتها؟ عندما تُظهر المقارنة ، على سبيل المثال ، أن Java لا تدعم الوراثة المتعددة وأن C ++ تدعم ، ماذا يعني ذلك؟ وهل هو شيء جيد؟ يجادل البعض بأن هذه ميزة لـ Java ، بينما يعلن البعض الآخر أنها مشكلة.

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

دراسة الأساسيات: بناء اللغة والنظم البيئية

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

تم إصدار Java لأول مرة في عام 1995 ، ولم يتم إنشاء Java مباشرة إلى الكود الأصلي. بدلاً من ذلك ، تُنشئ Java رمز بايت ، وهو تمثيل ثنائي متوسط ​​يعمل على Java Virtual Machine (JVM). بعبارة أخرى ، يحتاج إخراج مترجم Java إلى ملف تنفيذي أصلي خاص بالنظام الأساسي لتشغيله.

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

المقايضات التفصيلية: علم الوراثة والذاكرة والمزيد

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

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

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

سقسقة

بالنسبة لبعض المشاريع ، قد لا يكون الخيار واضحًا ، لذلك دعونا نقارن أكثر:

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


بصرف النظر عن الميزات التي تمت مقارنتها في الجدول ، سنركز أيضًا على ميزات البرمجة الموجهة للكائنات (OOP) مثل الوراثة المتعددة ، والعوامل العامة / القوالب ، والانعكاس. لاحظ أن كلتا اللغتين تدعمان OOP: تتطلب Java ذلك ، بينما تدعم C ++ OOP جنبًا إلى جنب مع الوظائف العالمية والبيانات الثابتة.

تعدد الميراث

في OOP ، الميراث هو عندما ترث فئة فرعية سمات وطرق من فئة أصل. أحد الأمثلة القياسية هو فئة 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 ++ الوراثة المتعددة ؛ جافا لا تفعل ذلك. الوراثة المتعددة مفيدة في حالات حافة معينة ، مثل:

  • إنشاء لغة متقدمة خاصة بالمجال (DSL).
  • إجراء عمليات حسابية معقدة في وقت الترجمة.
  • تحسين أمان نوع المشروع بطرق غير ممكنة في Java.

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

علم الوراثة والقوالب

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

في المقابل ، عند استخدام الأدوية الجنيسة ، يقوم برنامج التحويل البرمجي لـ Java بإنشاء كائنات عامة بدون أنواع من خلال عملية تسمى محو النوع. تقوم Java بإجراء فحص للنوع أثناء التحويل البرمجي ، لكن المبرمجين لا يمكنهم تعديل سلوك فئة أو طريقة عامة بناءً على معلمات النوع الخاصة بها. لفهم هذا بشكل أفضل ، دعنا نلقي نظرة على مثال سريع لوظيفة عامة 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);

سينتج C ++ وظيفة format std::string format(std::string fmt, std::string item1, int item2) ، بينما تقوم Java بإنشائها بدون string المحددة وأنواع الكائنات int للعنصر item2 item1 في هذه الحالة ، يعرف قالب C ++ الخاص بنا أن المعلمة الواردة الأخيرة هي int ، وبالتالي يمكن إجراء تحويل std::to_string الضروري في استدعاء format الثاني. بدون قوالب ، فإن عبارة C ++ printf التي تحاول طباعة رقم كسلسلة كما في استدعاء format الثاني سيكون لها سلوك غير محدد ويمكن أن تتسبب في تعطل التطبيق أو طباعة البيانات المهملة. ستكون وظيفة Java قادرة فقط على التعامل مع رقم كسلسلة في استدعاء format الأول ولن يتم تنسيقه كعدد صحيح سداسي عشري مباشرةً. هذا مثال تافه ، لكنه يوضح قدرة C ++ على تحديد قالب متخصص للتعامل مع أي كائن فئة تعسفي دون تعديل فئته أو وظيفة format . يمكننا إنتاج المخرجات بشكل صحيح في Java باستخدام الانعكاس بدلاً من الأدوية الجنيسة ، على الرغم من أن هذه الطريقة أقل قابلية للتوسعة وأكثر عرضة للخطأ.

انعكاس

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

لا تحتوي C ++ على انعكاس كامل ، ولكن C ++ الحديثة تقدم معلومات عن نوع وقت التشغيل (RTTI). يسمح RTTI باكتشاف وقت التشغيل لأنواع كائنات معينة ، على الرغم من أنه لا يمكنه الوصول إلى معلومات مثل أعضاء الكائن.

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

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

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

بينما لا تقدم Java سوى تخصيص الكومة ، فإن C ++ تدعم كلاً من تخصيص الكومة (باستخدام وظائف new delete أو وظائف C malloc الأقدم) وتخصيص المكدس. يمكن أن يكون تخصيص المكدس أسرع وأكثر أمانًا من تخصيص الكومة لأن المكدس عبارة عن بنية بيانات خطية بينما تكون الكومة قائمة على الأشجار ، لذا فإن تخصيص ذاكرة المكدس أسهل بكثير في التخصيص والإصدار.

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

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

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

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

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

ما تفتقر إليه Java في الأداء ، فإنه يعوض عن سهولة الاستخدام. أحد المكونات التي تؤثر على كفاءة المطور هو إدارة البناء والحزم - كيف نبني المشاريع ونجلب التبعيات الخارجية إلى التطبيق. في Java ، تعمل أداة تسمى Maven على تبسيط هذه العملية في بضع خطوات سهلة وتتكامل مع العديد من IDEs مثل IntelliJ IDEA.

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

الود للمبتدئين

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

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

وقت القرار: C ++ أو Java؟

مخطط انسيابي مع فقاعة "ابدأ" زرقاء داكنة في الزاوية العلوية اليسرى والتي تتصل في النهاية بواحد من سبعة مربعات خاتمة زرقاء فاتحة تحتها ، عبر سلسلة من تقاطعات القرار البيضاء مع فروع زرقاء داكنة لـ "نعم" وخيارات أخرى ، والفروع باللون الأزرق الفاتح لـ "No." الأول هو "تطبيق واجهة المستخدم الرسومية عبر الأنظمة الأساسية؟" والتي تشير "نعم" منها إلى الاستنتاج ، "اختر بيئة تطوير عبر الأنظمة الأساسية واستخدم لغتها الأساسية." تشير كلمة "لا" إلى "تطبيق Android الأصلي؟" ومنه تشير كلمة "نعم" إلى سؤال ثانوي "هل هي لعبة؟" من السؤال الثانوي ، تشير "لا" إلى الاستنتاج ، "استخدم Java (أو Kotlin)" و "نعم" تشير إلى نتيجة مختلفة ، "اختر محرك ألعاب متعدد المنصات واستخدم لغته الموصى بها." من "تطبيق Android الأصلي؟" سؤال ، "لا" يشير إلى "تطبيق Windows الأصلي؟" ومنه تشير كلمة "نعم" إلى سؤال ثانوي "هل هي لعبة؟" من السؤال الثانوي ، تشير "نعم" إلى الاستنتاج ، "اختر محرك ألعاب متعدد المنصات واستخدم لغته الموصى بها" ، وتشير "لا" إلى نتيجة مختلفة ، "اختر بيئة Windows GUI واستخدمها الأساسي اللغة (عادةً C ++ أو C #). " من "تطبيق Windows الأصلي؟" سؤال ، "لا" يشير إلى "تطبيق الخادم؟" من الذي يشير "نعم" إلى سؤال ثانوي ، "نوع المطور؟" من السؤال الثانوي ، يشير قرار "متوسط ​​المهارة" إلى الاستنتاج ، "استخدم Java (أو C # أو TypeScript)" ، ويشير قرار "ماهر" إلى سؤال جامعي ، "أولوية قصوى؟" من السؤال الثالث ، يشير قرار "إنتاجية المطور" إلى الاستنتاج ، "استخدم Java (أو C # أو TypeScript)" ، ويشير قرار "الأداء" إلى نتيجة مختلفة ، "استخدم C ++ (أو Rust)." من "تطبيق الخادم؟" سؤال ، "لا" يشير إلى سؤال ثانوي ، "تطوير السائق؟" من السؤال الثانوي ، تشير "نعم" إلى الاستنتاج ، "استخدم C ++ (أو Rust)" ، وتشير "لا" إلى سؤال من الدرجة الثالثة ، "تطوير إنترنت الأشياء؟" من السؤال الثالث ، تشير "نعم" إلى الاستنتاج ، "استخدم C ++ (أو Rust)" ، وتشير "لا" إلى سؤال رباعي ، "تداول عالي السرعة؟" من السؤال الرباعي ، تشير "نعم" إلى الاستنتاج ، "استخدم C ++ (أو Rust)" ، وتشير "لا" إلى الاستنتاج النهائي المتبقي ، "اسأل شخصًا على دراية بالمجال المستهدف."
دليل موسع لاختيار أفضل لغة لأنواع المشاريع المختلفة.

الآن بعد أن اكتشفنا الاختلافات بين C ++ و Java بعمق ، نعود إلى سؤالنا الأصلي: C ++ أو Java؟ حتى مع الفهم العميق للغتين ، لا توجد إجابة واحدة تناسب الجميع.

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

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