Classes و Objects — OOP في C++
الأصناف والكائنات (Classes & Objects)
في دورة الأساسيات كانت البيانات والدوال منفصلة: متغيرات هنا، ودوال تستقبلها هناك. الصنف (class) يجمع البيانات والسلوك الذي يعمل عليها في وحدة واحدة، ويضمن أن البيانات تبقى صحيحة دائماً.
- الصنف: المخطط (مثل مخطط بيت).
- الكائن (object): نسخة حقيقية من الصنف في الذاكرة (البيت المبني).
أول صنف
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
string name;
int grade = 0; // قيمة افتراضية للعضو
void print() const { // دالة عضو
cout << name << ": " << grade << '\n';
}
}; // لا تنسَ الفاصلة المنقوطة!
int main() {
Student s; // كائن
s.name = "سارة";
s.grade = 95;
s.print(); // سارة: 95
}class مقابل struct
الفرق الوحيد: الوصول الافتراضي.
| | الوصول الافتراضي للأعضاء | الوراثة الافتراضية |
|---|---|---|
| class | private | private |
| struct | public | public |
العرف الشائع: struct لتجميع بيانات بسيطة بلا قواعد (مثل نقطة x,y)، و class عندما توجد قواعد يجب حمايتها.
المُنشئ (Constructor)
دالة بنفس اسم الصنف، بلا نوع إرجاع، تُستدعى تلقائياً عند إنشاء الكائن لتهيئته.
#include <iostream>
#include <string>
using namespace std;
class Point {
double x, y;
public:
Point() : x(0), y(0) {} // مُنشئ افتراضي
Point(double x, double y) : x(x), y(y) {} // قائمة التهيئة
void print() const { cout << "(" << x << ", " << y << ")\n"; }
};
int main() {
Point a; // (0, 0)
Point b(3, 4); // (3, 4)
Point c{1.5, 2}; // التهيئة بالأقواس المعقوصة تعمل أيضاً
a.print(); b.print(); c.print();
}لماذا قائمة التهيئة `: x(x), y(y)`؟
- الأعضاء تُهيَّأ قبل دخول جسم المُنشئ. الإسناد داخل الجسم يعني تهيئة ثم إسناداً (عمل مضاعف).
- الأعضاء من نوع const أو المراجع لا يمكن إسنادها، فقط تهيئتها في القائمة.
- تُهيَّأ الأعضاء بترتيب تصريحها في الصنف، لا بترتيب كتابتها في القائمة. اكتبها بنفس الترتيب لتجنب المفاجآت.
explicit
مُنشئ بمعامل واحد يسمح بتحويل ضمني: Meters m = 5;. أضف explicit لمنع التحويلات غير المقصودة:
class Meters {
double value;
public:
explicit Meters(double v) : value(v) {}
double get() const { return value; }
};
// Meters m = 5; ← لا يُترجم بسبب explicit
// Meters m(5); ← صحيحالمدمّر (Destructor)
~Student() يُستدعى تلقائياً عند انتهاء عمر الكائن. نادراً ما تحتاج كتابته إلا إذا كان الصنف يملك مورداً (ذاكرة، ملف…) — موضوع درس "دورة حياة الكائن".
الدوال const
void print() const وعد بأن الدالة لا تعدّل الكائن. على كائن const (أو مرجع const Student&) لا يمكن استدعاء إلا الدوال const. اجعل كل دالة لا تعدّل شيئاً const — المترجم سيحميك.
المؤشر this
داخل الدالة العضو، this مؤشر إلى الكائن الحالي. تحتاجه عند تشابه الأسماء أو لإرجاع الكائن نفسه:
class Counter {
int value = 0;
public:
Counter& add(int n) { value += n; return *this; } // يسمح بالسلسلة
int get() const { return value; }
};
// Counter c; c.add(2).add(3); → c.get() == 5فصل التصريح عن التعريف
في المشاريع الحقيقية يوضع الصنف في ملف رأس Point.h والتعريفات في Point.cpp:
// Point.h
class Circle {
double r;
public:
explicit Circle(double r);
double area() const;
};
// Point.cpp
Circle::Circle(double r) : r(r) {}
double Circle::area() const { return 3.14159 * r * r; }العامل :: يقول: "هذه الدالة تابعة للصنف Circle".
أخطاء شائعة
- نسيان ; بعد } الصنف ← أخطاء مترجم غريبة في السطر التالي.
- Point p(); لا تنشئ كائناً! يفهمها المترجم تصريحاً عن دالة تعيد Point. استخدم Point p; أو Point p{};.
- نسيان const على دوال القراءة ثم العجز عن استدعائها عبر const&.
تمرين
اكتب صنف Rectangle بعرض وارتفاع (private)، مُنشئ بقائمة تهيئة، ودوال area() const و perimeter() const و scale(double f). أنشئ مستطيلين واطبع مساحتيهما.
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
int age;
public:
// Constructor
Person(string n, int a) : name(n), age(a) {
cout << "Person created: " << name << endl;
}
// Default constructor
Person() : name("Unknown"), age(0) {}
// Destructor
~Person() {
cout << "Person destroyed: " << name << endl;
}
// Getter methods
string getName() const { return name; }
int getAge() const { return age; }
// Setter methods
void setName(string n) { name = n; }
void setAge(int a) {
if (a >= 0) age = a;
}
// Method
void introduce() const {
cout << "مرحباً، أنا " << name << " وعمري " << age << endl;
}
};
class Rectangle {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const {
return width * height;
}
double perimeter() const {
return 2 * (width + height);
}
// Static member
static int count;
// Friend function
friend void printDimensions(const Rectangle& r);
};
int Rectangle::count = 0;
void printDimensions(const Rectangle& r) {
cout << "Width: " << r.width << ", Height: " << r.height << endl;
}
int main() {
Person p1("أحمد", 25);
p1.introduce();
Person p2;
p2.setName("محمد");
p2.setAge(30);
p2.introduce();
Rectangle rect(10, 5);
cout << "Area: " << rect.area() << endl;
cout << "Perimeter: " << rect.perimeter() << endl;
return 0;
}اختبر فهمك