ما هو الصنف
الصنف هو مخطط لنوع مخصص يجمع البيانات (المتغيرات الأعضاء) والسلوك (الدوال الأعضاء) في وحدة واحدة. وبينما تصف الأنواع المدمجة مثل int وdouble قيمًا مفردة، يتيح لك الصنف نمذجة مفهوم كامل - حساب بنكي، أو نقطة ثنائية الأبعاد، أو لاعب - كقيمة واحدة يمكنك تمريرها.
لقد استخدمت الأصناف بالفعل دون أن تعرّف أيًّا منها: فـ std::string وstd::vector هما صنفان من المكتبة القياسية. واستدعاء name.length() أو v.push_back(3) هو استدعاء دالة عضو على كائن. والآن ستبني أنواعك الخاصة بالطريقة نفسها، ثم تنتقل إلى المُنشئات لتهيئتها.
تعريف صنف وإنشاء كائنات
يَسرد تعريف الصنف أعضاءه بين قوسين معقوفين وينتهي بفاصلة منقوطة - ونسيان تلك ; الختامية من أكثر أخطاء المبتدئين شيوعًا. وكل كائن تنشئه من الصنف يحصل على نسخته الخاصة من المتغيرات الأعضاء.
rex وluna كائنان منفصلان. وتغيير rex.name لا يمس luna.name - فكل كائن يحتفظ ببياناته الخاصة. تصل إلى عضوٍ ما باستخدام معامل النقطة . على كائن (أو -> عندما يكون لديك مؤشر إليه).
public مقابل private
افتراضيًا، كل شيء في class هو private: لا يستطيع لمسه سوى الدوال الأعضاء للصنف نفسه. واللصيقة public: تفتح الأعضاء للشيفرة الخارجية. وهذا الفصل هو جوهر التغليف - إذ تعرض واجهة آمنة وتُخفي البيانات الداخلية كي لا يمكن إيقاعها في حالة غير صالحة.
لأن count هو private، لا يستطيع أي مستدعٍ تمرير قيمة سالبة أو غير منطقية خِلسةً - بل عليه المرور عبر increment(). وكلمة const بعد قائمة معاملات value() تَعِد بأن الدالة لن تعدّل الكائن، مما يتيح استدعاءها على كائنات const ويوضّح النية لمن يقرأ الشيفرة. راجع محددات الوصول للقصة الكاملة حول public وprivate وprotected.
الإعلان مقابل التعريف للدوال
في الأصناف الصغيرة، تعريف الدوال ضمن الصنف مباشرةً (كما سبق) أمر جيد. أما في الأصناف الأكبر، فمن الشائع الإعلان عن الدوال داخل الصنف وتعريف أجسامها خارجه باستخدام صياغة النطاق ClassName::method. وهذا يُبقي تعريف الصنف سهل القراءة كملخّص للواجهة.
تُخبر البادئة Rectangle:: المترجمَ بالصنف الذي تنتمي إليه الدالة. وداخل تعريف كهذا تظل تشير إلى الأعضاء بأسمائها المجرّدة (width، area()) - فالمترجم يعرف أنها تنتمي إلى الكائن الذي استُدعيت عليه الدالة.
مؤشر this
داخل أي دالة عضو غير ساكنة، يكون this مؤشرًا إلى الكائن الذي استُدعيت عليه الدالة. وعادةً لا تحتاجه، لأن اسم العضو المجرّد يشير أصلًا إلى الكائن الحالي. ويثبت فائدته عندما يحجب معامِلٌ عضوًا (shadow) - إذ يتشاركان الاسم نفسه - فيلزمك إزالة الالتباس.
من دون this->، فإن كتابة x = x; داخل setX تُسنِد المعامل إلى نفسه وتترك العضو دون مساس - وهو خطأ صامت. وتجعلها this->x لا لبس فيها. كثير من قواعد الشيفرة تتجنب المشكلة تمامًا بتسمية المعاملات تسميةً مختلفة (مثل setX(int newX))، لكنك سترى this-> باستمرار في الشيفرة الحقيقية.
أخطاء شائعة
حفنة من مزالق الأصناف توقع الناس مرارًا وتكرارًا:
- نسيان الفاصلة المنقوطة الختامية. ينتهي تعريف الصنف بـ
};. وإذا أسقطت;فستحصل على سيل من الأخطاء المربكة التي تشير إلى ما يأتي بعد الصنف، لا إلى الصنف نفسه. - نسيان تهيئة الأعضاء. الأعضاء من الأنواع المدمجة مثل
intوdoubleلا تُصفَّر تلقائيًا. وقراءة عضو غير مهيأ سلوك غير معرّف. أعطِ الأعضاء قيمًا افتراضية (int count = 0;) أو هيّئها في مُنشئ. - الوصول إلى أعضاء private من الخارج.
c.count = 5;على عضو private هو خطأ ترجمة - مُرَّ عبر دالة عامة بدلًا من ذلك. وهذا هو التغليف يعمل كما هو مقصود. - الخلط بين الصنف والكائن.
Dog.bark();خطأ - فـDogهو النوع. أنت تستدعي الدوال على الكائنات:rex.bark();.
// عضو غير مهيأ - قراءة 'age' سلوك غير معرّف:
class Cat {
public:
int age; // لا قيمة افتراضية
};
Cat c;
std::cout << c.age; // قيمة عشوائية لا قيمتها 0
التالي: المُنشئات
تعيين كل عضو يدويًا بعد إنشاء الكائن - rex.name = ...; rex.age = ...; - أمر مُمِل وسهل النسيان، وهذا بالضبط ما يقودك إلى أعضاء غير مهيأة. تتناول الصفحة التالية المُنشئات: دوال خاصة تُنفَّذ تلقائيًا عند إنشاء الكائن، تتيح لك ضمان أن يبدأ كل كائن في حالة صالحة بصياغة نظيفة من سطر واحد Dog rex("Rex", 4);.
الأسئلة الشائعة
ما الفرق بين الصنف والكائن في ++C؟
الصنف هو المخطط - يصف ما يملكه النوع من بيانات (متغيرات أعضاء) وسلوك (دوال أعضاء). أما الكائن فهو نسخة محددة مبنية من ذلك المخطط. تعرّف class Dog { ... }; النوع مرة واحدة؛ بينما تُنشئ Dog rex; كائن Dog فعليًا يمكنك استخدامه. الصنف الواحد قادر على إنتاج عدة كائنات مستقلة عن بعضها.
ما الفرق بين class و struct في ++C؟
من الناحية التقنية، الفرق هو مستوى الوصول الافتراضي فقط: أعضاء class هي private افتراضيًا، بينما أعضاء struct هي public افتراضيًا. يمكن لكليهما امتلاك دوال أعضاء ومُنشئات ووراثة. وفق العُرف، تُستخدم struct لتجميع البيانات البسيطة، وclass للأنواع ذات السلوك والثوابت التي ينبغي حمايتها.
ماذا يفعل مؤشر this في صنف ++C؟
داخل أي دالة عضو، يكون this مؤشرًا إلى الكائن الذي استُدعيت عليه الدالة. استخدمه للتمييز بين معامِل وعضو (this->x = x;) أو لإرجاع الكائن الحالي. نادرًا ما تحتاجه للوصول العادي إلى الأعضاء - فكتابة x تشير أصلًا إلى x الخاص بالكائن الحالي.