Kodax

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;
}
اختبر فهمك