Operator Overloading — OOP في C++
تحميل العوامل (Operator Overloading)
تحميل العوامل يجعل الكود الذي يستخدم أصنافك طبيعياً: a + b بدلاً من a.add(b). لكنه مجرد دالة باسم خاص: a + b تعني operator+(a, b) أو a.operator+(b).
القاعدة الذهبية
حمّل العامل فقط إذا كان معناه بديهياً ومتوقعاً. + لمتجهين واضح؛ + لإضافة موظف إلى قسم غير واضح — استخدم دالة باسم.
مثال متكامل: Vec2
#include <iostream>
using namespace std;
class Vec2 {
public:
double x = 0, y = 0;
Vec2() = default;
Vec2(double x, double y) : x(x), y(y) {}
// العوامل المركبة تعدّل الكائن ← عضو، وتُرجع مرجعاً
Vec2& operator+=(const Vec2& o) { x += o.x; y += o.y; return *this; }
Vec2& operator*=(double k) { x *= k; y *= k; return *this; }
Vec2 operator-() const { return {-x, -y}; } // سالب أحادي
bool operator==(const Vec2&) const = default; // C++20: يولّد == و != تلقائياً
};
// العوامل الثنائية المتماثلة ← دوال حرة مبنية على المركبة
Vec2 operator+(Vec2 a, const Vec2& b) { a += b; return a; }
Vec2 operator*(Vec2 v, double k) { v *= k; return v; }
Vec2 operator*(double k, Vec2 v) { v *= k; return v; } // ليعمل 2 * v أيضاً
// << يجب أن تكون حرة: الطرف الأيسر ostream وليس Vec2
ostream& operator<<(ostream& os, const Vec2& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
int main() {
Vec2 a(1, 2), b(3, 4);
cout << a + b << '\n'; // (4, 6)
cout << 2 * a << '\n'; // (2, 4)
cout << -b << '\n'; // (-3, -4)
a += b;
cout << boolalpha << (a == Vec2(4, 6)) << '\n'; // true
}عضو أم دالة حرة؟
| العامل | الشكل المفضل | السبب |
|---|---|---|
| = [] () -> | عضو (إجباري) | قاعدة اللغة |
| += -= *= | عضو | يعدّل الكائن الأيسر |
| + - * / | حرة | ليعمل التحويل على الطرفين (2 * v) |
| << >> للتدفقات | حرة | الطرف الأيسر ostream/istream |
| == <=> | عضو = default أو حرة | C++20 يولّد الباقي |
بناء + على += يمنع تكرار المنطق.
المقارنة في C++20
auto operator<=>(const T&) const = default; يولّد < <= > >= بمقارنة الأعضاء بالترتيب، و = default على == يولّد == و !=. قبل C++20 كان يلزم كتابة ستة عوامل.
#include <compare>
#include <string>
using namespace std;
struct Version {
int major, minor, patch;
auto operator<=>(const Version&) const = default;
};
// Version{1,2,0} < Version{1,10,0} → trueعامل الفهرسة []
وفّر نسختين: واحدة للكائنات العادية تُرجع مرجعاً قابلاً للتعديل، وواحدة const:
#include <cstddef>
class Row {
int cells[4] = {};
public:
int& operator[](std::size_t i) { return cells[i]; }
const int& operator[](std::size_t i) const { return cells[i]; }
};قيود
- لا يمكن تحميل: :: . .* ?: sizeof.
- لا يمكن تغيير الأولوية ولا عدد المعاملات ولا اختراع عوامل جديدة.
- لا تحمّل && و ||: النسخة المحمّلة تفقد التقييم المختصر (short-circuit).
تمرين
اكتب صنف Fraction (بسط ومقام) مع + و * و == و <<. اختصر الكسر في المُنشئ باستخدام std::gcd من <numeric>، وتأكد أن Fraction(1,2) == Fraction(2,4).
#include <iostream>
#include <cmath>
using namespace std;
class Vector2D {
private:
double x, y;
public:
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// Getters
double getX() const { return x; }
double getY() const { return y; }
// الجمع
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// الطرح
Vector2D operator-(const Vector2D& other) const {
return Vector2D(x - other.x, y - other.y);
}
// الضرب في عدد
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
// القسمة على عدد
Vector2D operator/(double scalar) const {
if (scalar == 0) throw runtime_error("قسمة على صفر!");
return Vector2D(x / scalar, y / scalar);
}
// المساواة
bool operator==(const Vector2D& other) const {
return x == other.x && y == other.y;
}
bool operator!=(const Vector2D& other) const {
return !(*this == other);
}
// +=
Vector2D& operator+=(const Vector2D& other) {
x += other.x;
y += other.y;
return *this;
}
// السالب
Vector2D operator-() const {
return Vector2D(-x, -y);
}
// الفهرسة
double operator[](int index) const {
if (index == 0) return x;
if (index == 1) return y;
throw out_of_range("فهرس غير صالح!");
}
// الطول
double magnitude() const {
return sqrt(x*x + y*y);
}
// Friend للطباعة
friend ostream& operator<<(ostream& os, const Vector2D& v);
friend istream& operator>>(istream& is, Vector2D& v);
};
// عامل الطباعة
ostream& operator<<(ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
// عامل الإدخال
istream& operator>>(istream& is, Vector2D& v) {
is >> v.x >> v.y;
return is;
}
// ضرب عدد في Vector
Vector2D operator*(double scalar, const Vector2D& v) {
return v * scalar;
}
int main() {
Vector2D v1(3, 4);
Vector2D v2(1, 2);
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
// العمليات الحسابية
cout << "\n=== العمليات ===" << endl;
cout << "v1 + v2 = " << (v1 + v2) << endl;
cout << "v1 - v2 = " << (v1 - v2) << endl;
cout << "v1 * 2 = " << (v1 * 2) << endl;
cout << "3 * v2 = " << (3 * v2) << endl;
cout << "v1 / 2 = " << (v1 / 2) << endl;
cout << "-v1 = " << (-v1) << endl;
// المقارنة
cout << "\n=== المقارنة ===" << endl;
cout << "v1 == v2: " << (v1 == v2 ? "نعم" : "لا") << endl;
cout << "v1 != v2: " << (v1 != v2 ? "نعم" : "لا") << endl;
// الفهرسة
cout << "\n=== الفهرسة ===" << endl;
cout << "v1[0] = " << v1[0] << endl;
cout << "v1[1] = " << v1[1] << endl;
// +=
cout << "\n=== += ===" << endl;
v1 += v2;
cout << "v1 بعد += v2: " << v1 << endl;
// الطول
cout << "\n=== الطول ===" << endl;
Vector2D v3(3, 4);
cout << "|" << v3 << "| = " << v3.magnitude() << endl;
return 0;
}اختبر فهمك