تخطَّ إلى المحتوى

الكلاسات في C++‎: البرمجة كائنية التوجه

الدرس 7 من 11 · C++

الفرق الأكبر بين C و C++‎ هو البرمجة كائنية التوجه (Object-Oriented Programming). الاسم الأول للغة كان «C with Classes»، أي «C مع الكلاسات». الكلاسات تسمح لك بجمع البيانات والدوال التي تعمل عليها في وحدة واحدة، وهذا ما يجعل برامج ضخمة مثل الألعاب والمتصفحات منظمة وقابلة للتطوير.

class Student {
private:
string name;
int age;
public:
Student(string n, int a) : name(n), age(a) {}
void introduce() const {
cout << "أنا " << name << " وعمري " << age << " سنة" << endl;
}
};
int main() {
Student s1("سارة", 14);
Student s2("عمر", 15);
s1.introduce();
s2.introduce();
return 0;
}
الناتج
أنا سارة وعمري 14 سنة
أنا عمر وعمري 15 سنة
  • الكلاس قالب، والكائن نسخة حقيقية منه: s1 و s2 كائنان، لكل منهما بياناته.
  • private: الأعضاء بعدها لا يمكن الوصول إليها من خارج الكلاس.
  • public: الأعضاء بعدها متاحة للجميع.
  • لاحظ الفاصلة المنقوطة بعد القوس الأخير للكلاس };، ونسيانها خطأ شائع جداً.

المُنشئ وقائمة التهيئة

Section titled “المُنشئ وقائمة التهيئة”
Student(string n, int a) : name(n), age(a) {}

المُنشئ (Constructor) دالة اسمها نفس اسم الكلاس وبلا نوع مرجع، وتُنفَّذ عند إنشاء الكائن. والجزء بعد النقطتين : name(n), age(a) يسمى قائمة التهيئة (Initializer List)، ويعطي الحقول قيمها الأولى مباشرة. وهي الطريقة المفضلة في C++‎، والقوسان المعقوفان {} بعدها فارغان لأنه لا يوجد عمل إضافي.

لاحظ const بعد introduce(). معناها: «هذه الدالة لا تغيّر بيانات الكائن». المترجم يمنعها من تعديل أي حقل، ويسمح باستدعائها على الكائنات الثابتة. اجعل كل دالة تقرأ فقط const، فهذه عادة احترافية في C++‎.

int getAge() const { return age; }
void setAge(int a) {
if (a < 0) {
cout << "العمر لا يمكن أن يكون سالباً" << endl;
return;
}
age = a;
}
s2.setAge(16);
s2.setAge(-3);
cout << s2.getAge() << endl;
الناتج
العمر لا يمكن أن يكون سالباً
16

الحقل age خاص، والتعديل يمر فقط عبر setAge التي تتحقق من القيمة. هذا هو التغليف (Encapsulation): مثل لوحة التحكم في السيارة، تقود عبر المقود والدواسات، ولا تعبث بالمحرك مباشرة.

struct: كلاس للبيانات البسيطة

Section titled “struct: كلاس للبيانات البسيطة”
struct Point {
int x;
int y;
};
Point p{3, 4};
cout << p.x << ", " << p.y << endl;
الناتج
3, 4

struct في C++‎ مثل class تماماً، والفرق الوحيد أن أعضاءه عامة افتراضياً. والعُرف: استخدم struct للبيانات البسيطة بلا قواعد خاصة، و class عندما تحتاج حماية البيانات وسلوكاً.

class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
virtual ~Animal() = default;
virtual void speak() const {
cout << name << " يصدر صوتاً" << endl;
}
};
class Cat : public Animal {
public:
Cat(string n) : Animal(n) {}
void speak() const override {
cout << name << " يقول: مياو" << endl;
}
};
  • class Cat : public Animal: القطة ترث من الحيوان.
  • Animal(n) في قائمة التهيئة: استدعاء مُنشئ الأب.
  • protected: مثل private، لكن الكلاسات الوارثة تستطيع الوصول.
  • virtual: تسمح للكلاسات الوارثة بإعادة تعريف الدالة، بحيث يُستدعى السلوك الصحيح حسب نوع الكائن الحقيقي.
  • override: تؤكد للمترجم أنك تعيد تعريف دالة virtual، فينبهك إذا أخطأت في اسمها أو معاملاتها.
  • virtual ~Animal() = default;: المُدمِّر (Destructor) الافتراضي. أي كلاس فيه دوال virtual يجب أن يكون مدمّره virtual أيضاً، لتُحذف الكائنات الوارثة بشكل صحيح.

واستخدامها معاً:

#include <memory>
vector<unique_ptr<Animal>> animals;
animals.push_back(make_unique<Animal>("الحصان"));
animals.push_back(make_unique<Cat>("مشمش"));
for (const auto& a : animals) {
a->speak();
}
الناتج
الحصان يصدر صوتاً
مشمش يقول: مياو

unique_ptr مؤشر ذكي: يحمل الكائن ويحذفه من الذاكرة تلقائياً عندما لا يعود مطلوباً، فلا تحتاج new و delete اليدوية التي سببت أخطاء كثيرة في الماضي. و -> تستدعي الدالة عبر المؤشر. والنتيجة: قائمة واحدة فيها أنواع مختلفة، وكل كائن يتصرف حسب نوعه الحقيقي. هذه الميزة اسمها تعدد الأشكال (Polymorphism).

  1. صمم كلاس BankAccount بحقل خاص balance، ودالتين deposit و withdraw ترفضان المبالغ غير المنطقية، ودالة getBalance() const.
  2. أنشئ حسابين، ونفذ عمليات، واطبع الأرصدة.
  3. أنشئ struct Product فيه الاسم والسعر، و vector<Product> من 3 منتجات، واطبع إجمالي الأسعار.
  4. تحدٍّ: أنشئ class Shape بدالة virtual double area() const، وكلاسين Circle و Rectangle يعيدان تعريفها، وضع أشكالاً في vector<unique_ptr<Shape>> واطبع مساحاتها.
  • نسيان الفاصلة المنقوطة بعد الكلاس: }; مطلوبة، وغيابها يعطي أخطاء محيرة في أسطر بعيدة.
  • نسيان virtual في الأب: تُستدعى دالة الأب دائماً بدلاً من دالة الابن عند التعامل عبر مؤشر للأب.
  • الوصول لحقل private من الخارج: خطأ is private within this context. استخدم دالة عامة.

سؤال

ما الفرق الوحيد بين struct و class في C++‎؟

سؤال

لماذا نكتب virtual قبل دالة speak في الكلاس الأب؟

  • الكلاس قالب يجمع البيانات والدوال، و private و public تحددان الوصول.
  • المُنشئ بقائمة التهيئة : name(n)، والدوال التي تقرأ فقط تُعلَّم بـ const.
  • التغليف: حقول خاصة مع دوال تتحقق من البيانات.
  • struct للبيانات البسيطة، والوراثة بـ : public Base مع virtual و override.
  • unique_ptr و make_unique لإدارة الذاكرة تلقائياً.