الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Exercise For Operator Overloading

بدأه CCSI في 6 مايو 2009 · 3 رد · 741 مشاهدة · في الأسئلة المجابة
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكــم ورحمـة الله وبركاتــه ،،

مســــآئكم/صبـــآحكم سعادة ورضى

اريد مثال على التحميل الزائد للعملية () و-> لأني بحث كثيرا عنها ولم اجد أمثلة واضحة

مو مشكلة لو المثال بسيط فقط اريد ان تتضح لي الصورة متى يجب علي ان اعمل تحميل زائد لها

شاكرة جهودكم المبذولهـ

~في انتظار ردكم ~ :blush: ـ

رْبَِ أِشًرح لِــِّي صِدرِيَِ وُيسَّرَِّ لّْي أِםـَـرًيِ ~

--۰•ஐ--------------❤---

#2

وعليكــم السـلام ورحمة الله وبركاتـه..

هناك دوال في سي تستقبل معاملات function pointer -مؤشر لداله- ، تعمل تلك الدوال كcallback function لعمل أمور معينه ،، سي++ اضافت ميزه أخرى وهي أن أي كائن يعيد تعريف المعامل () بالتالي يمكن أن نعتبره مثل المؤشر للداله وتسمى تلك الكائنات Functor أو Function Object ..

عندما تقوم بعمل Overload للمعامل () فإن الكائن من هذا الكلاس سوف يكون functor وبالتالى يمكن أن ترسله لأحدى الدوال التي تستقبل callback function .

نشاهد مثال بسيط :

#include <iostream>
using namespace std;

class FunctionObject {
	public:
		int square		(int a) {	return a*a; }
		int operator()	(int a) {	return a*a; }
};

int main (int argc ,  char[]){
	FunctionObject fo;

	cout << "using normal method : " << fo.square(3) << endl;
	cout << "using functor  : " << fo(2) << endl; 

	return (0);
}

لاحظ أنه أمكن الأن استدعاء الداله فقط من خلال اسم الكائن وتمرير المعامل الذي نريد ..

هل أستفدنا الأن بشيء حقيقي ،، هممم نعم ولا :) ..

بعض الأحيان قد تكون الفائده أكبر ، مثلا لدينا كلاس Matrix يحتوي على مصفوفه ثنائيه ،، بالطبع لن نستطيع عمل overload للمعامل [] [] فقط مره واحده ،، لذلك البعض يقوم بعمل Overload للمعامل () ويرسل لها الرقمين (رقم الصف ، والعمود) ..

الاهم هي في مكتبه STL الغنيه بهياكل البيانات ، ومجموعه من الخوارزميات غير متعلقه بأي نوع بيانات معين والتي تعمل على أي من هذه الcontainor ،، فاذا كان نوع الcontainor نص ، فالخوارزميات سوف تعمل على النصوص ، وهذا أجمل ما فيها (تستخدم بالطبع function templates)

هذه الخوارزميات تستقبل عاده Callback (سواء مؤشر لداله ، أو كائن يحتوي على اعاده لتعريف المعامل () ) .

نأخذ مثال بسيط ،، الداله accumulate تقوم بحساب حاصل جمع مجموعه من القيم ،،

#include <numeric>
#include <iostream>
#include <vector>
using namespace std;

double getMean (const vector<int>& nums) {
	double sum = accumulate(nums.begin(),nums.end(),0);
	return (sum/nums.size());
}

int main (int argc , char* argv[]){
	vector<int> arr;

	arr.push_back(10);
	arr.push_back(13);
	arr.push_back(4);
	arr.push_back(16);

	cout << "mean is: " << getMean(arr) << endl; 

	return (0);
}

لاحظ أرسلنا في الخانه الأولى للداله العنصر الأول ، والمعامل الثاني هو أخر عنصر في الفيكتور ، المعامل الأخير هو القيمه الابتدائيه للجمع ،،

الداله accumulate لها overload أخر ، حيث تستقبل معامل في الأخير يكون Callback عباره عن أي عمليه أخرى بخلاف الجمع ، مثلا نريد أن نجري الطرح ، علينا أن نكتب الداله أولا ، ثم نرسل اسم الداله للaccumulate :

#include <numeric>
#include <iostream>
#include <vector>
using namespace std;

int sub (int n1 , int n2) {
	return n1-n2;
}

double getMean (const vector<int>& nums) {
	double sum = accumulate(nums.begin(),nums.end(),0,sub);
	return (sum/nums.size());
}

int main (int argc , char* argv[]){
	vector<int> arr;

	arr.push_back(10);
	arr.push_back(13);
	arr.push_back(4);
	arr.push_back(16);

	cout << "mean is: " << getMean(arr) << endl; 

	return (0);
}

بعدما عرف مخترع سي++ أهميه هذه الcallback قام بعمل العديد من الكلاسات الجاهزه المعرفه مسبقا (تعيد تعريف () طبعا) والتي يمكن أن تعمل كcallback ، يمكنك استخدامها بدلا من كتابه واحد من الصفر ،،فمنها ما يقوم بعمليات حسابيه ومنها ما يقوم بعمليات مقارنه والكثير غيرهم ..

مثلا تحتوي سي++ على كلاسات للعمليات الحسابيه (الجمع ، الطرح ،،،الخ) ... مثال على الكلاس plus :

#include <functional>
#include <iostream>
using namespace std;

int main (int argc , char* argv[]) {
	plus<int> MyPlus;

	cout << "1+2 = " << MyPlus(1,2) << endl;

	return  (0);

جميل ، لنرى كيف يمكن أن نستخدم أحد الكلاسات الجاهزه كcallbacks لأحد الخوازرميات ولتكن الaccumulate (نفس المثال السابق ولكن باستخدام كلاس الطرح الجاهز minus ) :

#include <numeric>
#include <iostream>
#include <vector>
#include <functional>
using namespace std;


double getMean (const vector<int>& nums) {
	double sum = accumulate(nums.begin(),nums.end(),0,minus<int>());
	return (sum/nums.size());
}

int main (int argc , char* argv[]){
	vector<int> arr;

	arr.push_back(10);
	arr.push_back(13);
	arr.push_back(4);
	arr.push_back(16);

	cout << "mean is: " << getMean(arr) << endl; 

	return (0);
}

باختصار شديد أكثر استخدامات لاعاده تعريف المعامل () هي أن تعمل كcallback ،، فقط :) ..

أرجوا أن تكون واضحه الأن ..

بالنسبه لاعاده تعريف المعاملات deferences ، فسأحاول أن نتحدث عنه اليوم مساء ان شاء الله -ان وجد وقت- ، أو يضع أحد الشباب مثال لذلك .

بالتوفيق :) .

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

#3

السلام عليكم

اقرأى هذا الكود جيدا

#include <iostream>
using namespace std;
class Test
{
private:
	int a,b,c;
	Test* next;
public:
	Test():a(0),b(0),c(0){}
	Test(int a1,int b1,int c1)
	{
		a=a1;b=b1;c=c1;
		next=new Test;
		next->a=5;
		next->b=6;
		next->c=7;
	}
	void printa(){ cout<<a<<endl;}
	void printb(){ cout<<b<<endl;}
	void printc(){ cout<<c<<endl;}
	Test* operator->()
	{
		return next;
	}
};
int main()
{
	Test A(2,3,4);
	// this by dot operator 
	A.printa();
	A.printb();
	A.printc();
	// this by arrow operator
	cout<<"-----------------------------------------------------------\n";
	A->printa();
	A->printb();
	A->printc();
	return 0;
}

قمت بإعادة تعريف ل -> المسمى ب The arrow member selection operator

طبعا تعلمين انه يستخدم مع مؤشر الى كائن

ولكنى جعلته يتعامل مع الكائن مباشرة

وأسندت اليه وظيفة الوصول الى مؤشر داخل الكلاس

لاحظى خارج البرنامج

سلام

اللهم صل على سيدنا محمد وعلى آله وصحبه وسلم

#4

الأخ أمين -جزاه الله خيرا- لم يقصر كعادته وطرح مثال جيد حول اعاده تعريف ال-> والتي يجب أن ترجع عنوان لشيء ما حتى يستخدم هذا العنوان مع المعامل -> والذي يطلق عليه البعض بArrow Operator ..

نوسع الحديث قليلا حول معاملات Dereferencing بشكل عام ( * و -> و ->* :) ) ،، ونبدأ بالأسهل الأن ..

نبدأ بهذا المثال :

#include <iostream>
using namespace std;

class Demo {
	private :
		int x;

	public :
		Demo () : x(1) { }

		void setX (int a)  { x = a 	; }
		int  getX () const { return x; }
};

int main (int argc , char* argv[]) {
	Demo d;

	cout << d.getX () << endl;

	return (0);
}

عندما نقوم بعمل مؤشر من كائن لهذا النوع ،، يمكن أن نصل لأي داله فيه عن طريق ال Dereferencing ، كما بالشكل التالي :

Demo* d = new Demo();

cout << d->getX () << endl;
cout << (*d).getX() << endl;

حسنا ،، عندما نفكر في أننا نريد أن نعيد تعريف أحد هذه المعاملات ، فبالتأكيد لأننا نريد أن نعتبر ذلك الكائن عباره عن مؤشر حتى نستطيع ان نستخدم تلك المعاملات معه ،، الأمر الأخر في حال عدنا تعريف أحد تلك المعاملات فيفضل أن نعيد تعريف المعامل الأخر ، فمن غير المنطقى أن نستخدم مؤشر ونصل لمحتوياته بطريقه واحده فقط :) ..

نريد مثال لكي نقوم بتعريف هذه المعاملات وأفضل مثال ممكن هو Smart Pointer ،، اي مؤشر فقط نقوم بحجز مكان له ، ويقوم بتحرير الذاكره بنفسه عند انتهاء الكائن الذي يؤشر اليه ،،، نقوم بتطبيق Small Smart Pointer :

template <typename T>
class SmartPointer {
	protected :
		T* ptr;

	public :
		SmartPointer(T* p);
		~SmartPointer();

		// here will be derefrenceing operator 

	private :	// prevent pass by value && assignment operaor
		SmartPointer(const SmartPointer<T>& sp);
		SmartPointer<T>& operator = (const SmartPointer<T>& rhs);
};

template <typename T>
SmartPointer<T> :: SmartPointer(T* p) {
	ptr = p;
}

لنرى الأن كيف تكون داله main بشكل بسيط :

int main (int argc , char* argv[]) {
	SmartPointer<int> mySmartInt (new int);

	*mySmartInt = 5;
	cout << *mySmartInt << endl;

	SmartPointer<Demo> mySmartDemo(new Demo);
	mySmartDemo->setX(20);
	cout << (*mySmartDemo).getX() << endl;

	return (0);
}

يتضح من الداله السابقه أن استخدام هذه المعاملات أمر بسيط ، بالضبط كما تستخدم في المؤشرات تستخدم مع الكائنات ،، يبقى الأهم وهو كيف يتم تعريف هذه الدوال :

// here will be derefrenceing operator 
const T& operator* () const;
T& operator* ();

const T* operator-> () const;
T* operator-> ();

نبدأ بالداله * والتي تعني الوصول للمحتويات الموجوده بهذا العنوان ، اذا سيكون تعريفها بسيط للغايه :

template <typename T> 
T& SmartPointer<T> :: operator* () {
	return *ptr;
}

الداله الثانيه -> وهي حاله خاصه :) ، حيث عندما نقول هذه الجمله :

myDemo->setX(10);

فهذا يعني حقيقه :

(myDemo.operator->()).setX(10);

كأن ما بين القوسين سوف يكون عباره عن عنوان الكائن ، ونصل له عادي ،، اذا سنرجع العنوان هنا :

template <typename T> 
T* SmartPointer<T> :: operator-> ()  {
	return ptr;
}

template <typename T> 
const T* SmartPointer<T> :: operator-> () const {
	return ptr;
}

الكود النهائي للتجربه :

#include <iostream>
using namespace std;

class Demo {
	private :
		int x;

	public :
		Demo () : x(1) { }

		void setX (int a)  { x = a 	; }
		int  getX () const { return x; }
};


template <typename T>
class SmartPointer {
	protected :
		T* ptr;

	public :
		SmartPointer(T* p);
		~SmartPointer();

		// here will be derefrenceing operator 
		const T& operator* () const;
		T& operator* ();

		const T* operator-> () const;
		T* operator-> ();

	private :	// prevent pass by value && assignment operaor
		SmartPointer(const SmartPointer<T>& sp);
		SmartPointer<T>& operator = (const SmartPointer<T>& rhs);
};

template <typename T>
SmartPointer<T> :: SmartPointer(T* p) {
	ptr = p;
}

template <typename T>
SmartPointer<T> :: ~SmartPointer() {
	delete ptr;
}

template <typename T> 
T& SmartPointer<T> :: operator* () {
	return *ptr;
}

template <typename T> 
const T& SmartPointer<T> :: operator* () const {
	return *ptr;
}

template <typename T> 
T* SmartPointer<T> :: operator-> ()  {
	return ptr;
}

template <typename T> 
const T* SmartPointer<T> :: operator-> () const {
	return ptr;
}

int main (int argc , char* argv[]) {
	SmartPointer<int> mySmartInt (new int);

	*mySmartInt = 5;
	cout << *mySmartInt << endl;

	SmartPointer<Demo> mySmartDemo(new Demo);
	mySmartDemo->setX(20);
	cout << (*mySmartDemo).getX() << endl;

	return (0);
}

ماذا بعد ، ماذا عن ->* ؟ ...

نرجع لهذا المثال ولنرى :

#include <iostream>
using namespace std;

class Demo {
	private :
		int x;

	public :
		Demo () : x(1) { }

		void setX (int a)  { x = a 	; }
		int  getX () const { return x; }
};

int main (int argc , char* argv[]) {
	Demo d;

	int (Demo::*myPtr) () const = &Demo::getX;
	cout << (d.*myPtr) () << endl;

	return (0);
}

السطرين السابقين يعدوا من الحالات المستعصيه في سي++ ،، لا يوجد أحلى من جافا وأسلوبها السلس :lol: ،،،

على العموم قمنا بعمل مؤشر لداله داخل الكلاس ، وقمنا بالوصول للداله عن طريق هذا المؤشر ..

أعلاه قمنا بالوصول الى الداله عن طريق .* ، ويكمن الوصول لها بطريق أخر ، عن طريق ->* :

int main (int argc , char* argv[]) {
	Demo* d = new Demo();

	int (Demo::*myPtr) () const = &Demo::getX;
	cout << (d->*myPtr) () << endl;

	return (0);
}

أخيرا يمكنك عمل اعاده تعريف لهذا المعامل ->* ، لكن لا أعتقد أن هناك مبرمج يفضل أن يعيد تعريف مثل هذه اشاره :P ،، حتى القالب auto_ptr لا يعيد تعريف هذا المعامل ،،

كانت هذه لمحه بسيطه عن موضوع الoverloading ،، واعذرونا عن التقصير ..

والسلام عليكم ورحمه الله وبركاته

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

مواضيع مشابهة