المشغلات في ++C كالسحر: قواعد التحميل الزائد للمبتدئين

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا لا يبدو كودك كالسحر؟

\n\n

تخيل أنك تكتب برنامجًا بلغة ++C وتحتاج إلى مقارنة عددين مركبين. تكتب if (a > b) — ويصدر المترجم خطأ. "لماذا؟" تتساءل. لأن المترجم لا يعرف معنى "أكبر" لنوع البيانات المخصص الخاص بك.

\n\n

في لغة ++C، المشغلات ليست مجرد رموز على لوحة المفاتيح. إنها أداة قوية تسمح لكودك بالظهور بشكل طبيعي، كما في الرياضيات أو لغة Python. التحميل الزائد للمشغلات (operator overloading) هو وسيلة لتعليم المترجم كيفية العمل مع فئاتك بنفس البديهية التي يعمل بها مع الأنواع المضمنة. اليوم سنشرح القواعد الأساسية والأساليب الشائعة التي ستحول كودك من مجموعة مملة من الدوال إلى "لغة برمجة" أنيقة داخل ++C.

\n\n

هذه المادة مبنية على سؤال حقيقي من Stack Overflow حصل على آلاف المشاهدات. قمنا بتكييفها للمبتدئين، حتى لا تقوم فقط بنسخ الأمثلة، بل تفهم لماذا يجب أن تفعل بهذه الطريقة وليس بأخرى.

\n\n

القاعدة رقم 1: "لا تخترع العجلة" — متى يكون التحميل الزائد مبررًا

\n\n

أول شيء يجب أن تستوعبه: التحميل الزائد للمشغلات ليس حفلة يجب دعوة الجميع إليها. قم بتحميل المشغلات فقط عندما يجعل ذلك الكود أكثر وضوحًا. إذا كنت تنشئ فئة Vector — فإن تحميل + لجمع المتجهات أمر منطقي تمامًا. أما بالنسبة لفئة Database، فسيبدو المشغل + غريبًا ومضللاً.

\n\n

القاعدة الذهبية: يجب أن يكون دلالة المشغل بديهية. يجب أن يفهم مستخدم فئتك (أو أنت نفسك بعد شهر) من النظرة الأولى ما يفعله a + b.

\n\n

متى لا يجب التحميل الزائد

\n
    \n
  • إذا لم يكن للمشغل معنى واضح لنوعك (مثل operator+ لفئة "إنسان").
  • \n
  • إذا كان يمكنك الاستغناء بدالة عادية باسم معبر (add(), merge()).
  • \n
  • إذا كنت لا تخطط للحفاظ على الدلالة القياسية (يجب ألا يغير المشغل + معاملاته).
  • \n
\n\n

القاعدة رقم 2: مشغل الإسناد — ملك الأساليب

\n\n

المشغل الأكثر شيوعًا والأكثر مكرًا هو مشغل الإسناد (operator=). بدونه، ستعمل فئتك، ولكن فقط حتى تبدأ في نسخ الكائنات. وهنا يبدأ الجزء المثير للاهتمام.

\n\n

الأسلوب الأساسي لـ operator= هو copy-and-swap (النسخ والتبادل). لماذا هو جيد جدًا؟ لأنه يحل ثلاث مشاكل في آن واحد:

\n
    \n
  • الحماية من الإسناد الذاتي (a = a).
  • \n
  • الأمان من الاستثناءات (إذا ألقى النسخ استثناءً، يبقى الكائن الأصلي سليمًا).
  • \n
  • بساطة الكود وسهولة قراءته.
  • \n
\n\n

إليك كيف يبدو ذلك عمليًا:

\n\n
class MyString {\nprivate:\n    char* data;\n    size_t size;\n\npublic:\n    // مُنشئ النسخ\n    MyString(const MyString& other) : size(other.size) {\n        data = new char[size + 1];\n        std::copy(other.data, other.data + size + 1, data);\n    }\n\n    // المُدمّر\n    ~MyString() { delete[] data; }\n\n    // مشغل الإسناد عبر copy-and-swap\n    MyString& operator=(MyString other) { // 1. الاستلام بالقيمة\n        std::swap(data, other.data);       // 2. تبادل البيانات\n        std::swap(size, other.size);\n        return *this;                      // 3. إرجاع المرجع\n    } // 4. يتم تدمير الكائن القديم في مُدمّر other\n};
\n\n

لاحظ: يتم تمرير other بالقيمة، وليس بالمرجع. هذه هي النقطة الأساسية: مُنشئ النسخ يُنشئ نسخة مؤقتة، ونحن فقط نتبادل البيانات معها. يتم تحرير البيانات القديمة تلقائيًا عندما يتم تدمير الكائن المؤقت في

المدونات

توصيات الكتب