الخميس، 18 نوفمبر 2010

البرمجة الموجهة للكائنات , مقدمة عن مكتبة سي++ القياسية


 مقدمة عن مكتبة سي++ القياسية

لكتابة برنامج بلغة سي++ نعطي الإمتداد .cpp ولاستعمال مكتبة سي++ القياسية نستعمل الملف iostream.h وذلك ب #include واعلم أن هناك محاذير من الدمج بين وظائف مكتبةiotstream.h و stdio.h ولاستعمال مكتبة سي القياسية مع مكتبة سي++ نقوم بذلك من خلال ملف يبدأ ب c ثم اسم الملف دون .h مثلاً #include تصبح #include
برنامج "Hello, world!" في سي++ باستعمال مكتبة سي++ القياسية يكون
#include
int main() {
	cout << "Hello, world!\n";
	return 0;
}          
		
تعرف مكتبة سي++ كائن باسم cout و آخر باسم cin وآخر باسم cerr الأول يستخدم للإخراج والثاني للإدخال والأخير لعرض الأخطاء. الكائن هو متغير مركب كما في struct في سي، ولكن مع مزيد من التعقيدات. هذا الكائن ندخل لعناصرة الداخلية باستعمال العملية . أو -> إذا كان مؤشراً كما سنتعلم لاحقاً عن هذه الكائنات أنها تملك عمليات معرفة عليها بطريقة خاصة كما العمليات الحسابية منها عملية الإضافة insertion وهي على صيغة OBJECT << OBJECT2 أو السحب OBJECT >> OBJECT2 ومعنى هذه العمليات يعتمد على الكائن أكثر منه على العملية كما يختلف جمع الساعات عن جمع الأرقام12:00+1=01:00 الساعة الثانية عشرة + ساعة تصبح الواحدة . وبالنسبة للكائن الذي نتحدث عنه هنا cout فإن إضافة أي كائن له تظهره على جهاز الإخراج القياسي
باختصار لكتابة أي شيء على الشاشة ضعه بعد علامة الإضافة على يمين cout ويجوز تكرار العملية أكثر من مرة كما يلي
#include
int main() {
	cout << "Hello, "
		<<"world!"
		<
لأن ناتج عملية الإضافة return value هو عبارة عن مرجع ل cout لهذا يمكن عمل إضافة عليه. لاحظ هنا استخدامنا المتغير endl وهو متتغير معرف ضمن مكتبة سي++ القياسية وقيمته هي سطر جديد '\n'. هذا مثال آخر
#include
int main() {
	int mark1,mark2;
	cout << "Enter your 1st mark: m1=";
	cin  >> mark1;
	cout << "Enter your 2nd mark: m2=";
	cin  >> mark2;
	cout << "The sum of m1 and m2 is"<
هنا استخدمنا عملية السحب لأخذ الدخل من جهاز الدخل القياسي. أما بالنسبة للكائن cerr فاستعماله لا يختلف عن cout
إذا كنت تشعر بالغربة يمكنك استعمال اسلوب printf من خلال وظيفة form للكائن cout التي تستعمل هكذا cout.form(FORMAT,...); وبالنسبة ل scanf تقابلها الوظيفة scan للكائن cin كما يليcin.scan(FORMAT,...); والفائدة الأكبر لهما هي تنسيق الأرقام
	/* ... */
	int i;
	cout.form("The sum of m1 and m2 is %i \n",mark1+mark2);
	cout.form("I love printf %08.3f \n",1.5);
	cin.scan("I love scanf %d ",&i);
	/* ... */
		
وهي تشبه سي الكلاسيكية بحسناتها وسيئاتها
لقراءة سلسلة نصية يمكن استعمال cin.getline(str,len); حيث str هي مؤشر للسلسلة و len هو الحد الأعلى لطولها وهي تقابل fgets من مكتبة سي

7.3.2 مقدمة عن البرمجة الموجهة للكائنات

في سي كنا نتحدث عن متغير من نوع معين، في سي++ ستحدث عن كائن object من صنف معين class. وهي فكرة مطورة عن تركيب struct. ما يميّز الكائن عن المتغير هو أن الأول يحتوي على أكثر من مجرد بيانات data فقد يحتوي على وظائف function نسمي البيانات بخصائص properties و الوظائف بطرق methods ما يحددها هو صنف هذا الكائن فالكائنات من صنوف مختلفة تتجاوب بشكل مختلف لنفس العمليات. ويتم الوصول لأعضاء كائن عن طريق . أو -> في المؤشرات. والهدف منها هو تسهيل التطوير واختصار الكثير من طول الأسماء(في البداية ستشعر بأن الاختصار ليس هدفاً) وإخفاء التعقيدات الداخلية للكائن وإبعاد المبرمج الذي يريد أن يستعمل هذا الصنف في برامجه عن خفايا ما يجري بطريقة أكثر قرباً لفكرة التفاعل بين كائنات في الحياة العملية. لاحظ كيف كانت طباعة سلسلة نصية لا تختلف عن طباعة رقم عند إرسالهما إلى الكائن cout بنفس العملية ولكنها لا تتم بتلك السهولة داخلياً. بينما في سي كانت مهمة تحديد الوظيفة التي تناسب هذا أو ذاك تقع عليك .
لتعريف صنف جديد نستعمل الكلمة المفتاحية class أو struct ثم اسم هذا الصنف ثم متن الصنف والذي سيحتوي على تعريف/إعلان الخصائص والطرق أو نماذجها. ونعرّف كائن من صنف معين بكتابة اسم الصنف ثم اسم المتغير الذي هو اسم الكائن.
#include
class MyClass {
	int i;
	int get() {return i;}
	void set(int new_i) {i=new_i;}
};
int main() {
	MyClass myobj;
	int i;
	myobj.i=15; // access directly to member data(property)
	cout <<"myobj.i="<// access to member data(property) by a method
	cout <<"myobj.i="<return 0;
}
داخل متن الصنف لا حاجة لاستعمال . أو -> للوصول للخصائص أو الطرق.

7.3.3 اخفاء التعقيدات الداخلية

حتى نحقق هدفنا في إبعاد استخدام الصنف عن تعقيداته الداخلية فإنه يتوجب علينا تقسيم الأعضاء (الخصائص والطرق) إلى قسمين(في الحقيقة ثلاثة) منها ما هي عامة للاستخدام public ومنها ما هو داخلي وخاص بتعقيدات داخلية private يجب أن لا يعيرها مستعمل الصنف أي انتباه. ويكون ذلك بكتابة public: قبل الأعضاء العامة و privte: قبل الخاصة لمنع الوصول إليها إلا من داخل الصنف نفسه (نفصل لاحقاً) ومن أهداف هذا المنع هو إماكنية تتبع الوصول للخصائص والتحقق من القيم مثلاً
#include
class MyTime {
		public:
	int get_h() {return h;}
	int get_m() {return m;}
	int get_s() {return s;}
	void get(int &hh,int &mm,int &ss) {
		hh=h;	mm=m;	ss=s;
	}
	void set_h(int new_h) {h=new_h%24;}
	void set_m(int new_m) {m=new_m%60;}
	void set_s(int new_s) {s=new_s%60;}
	void set(int hh,int mm,int ss) {
		set_h(hh); set_m(mm); set_s(ss);
	}

		private:
	int h,m,s;
};
int main() {
	MyTime t;
	int i=0,j=0,k=0;
	// access directly to privte member property case error
	t.h=30; // remove this line 
	t.set(25,15,0); 
	t.get(i,j,k);
	cout << i << ':' << j << ':' << k <// access to member data(property) by a method
	cout <<"myobj.i="<return 0;
}
في هذا المثال رمزنا للساعات ب h ولولا تحديد الخصائص h و m و s بأنها private لكان السطر t.h=30; الذي يجعل الساعات 30 صحيحاً! وكيف ذلك وهي يجب أن تكون من 0 إلى 23 . طبعاً لن تنجح في تصنيف البرنامج؛ احذف ذلك السطر ثم حاول مرة أخرى. لاحظ أن الوصول للخصائص h و m و s يتم من خلال طرق مثل set التي تأخذ بعين الاعتبار أن تكون h أو m أو s قيم غير صحيحة لذلك تأخذ باقي القسمة طبعاً يمكن إعادة كتابة set_h لتصبح باستعمال الشرط بدل الباقي.
// ... 
	void set_h(int new_h) {
	if (new_h>=0&&new_h<24)
		h=new_h;
	else
		cerr <<"bad 'h'! 'h' must be [0-23].\n\a";
	}
// ... 

 تلميحيجوز أن تظهر public: أو private: أكثر من مرة داخل متن الصنف.

 تلميحالفرق بين استعمال class و struct في سي++ هو في كون الأولى تفترض أن الأعضاء التي لا تحدد نوعها تكون عامة public: و في الثانية تكون private:. ولكن لأغراض تسهيل القراءة لا تترك أعضاء دون تحديد .


7.3.4 خارج الصنف

يبدو الصف السابق وعلى الرغم من وظائفه القليلة مزعج بوجد متن (الوظيفة) داخل متن آخر (الصنف) لهذا ولأسباب تتعلق بتسهيل قراءة البرنامج يمكنا أن نضع وظائف الصنف خارج متنه باستعمال أداة تحديد المجال scope وهي '::' ويكون ذلك بذكر اسم الصنف ثم الوظيفة ومعاملاتها ومتنها. مع الإحتفاض بنموذج الوظيفة داخل متن الصنف
#include
class MyTime {
		public:
	int get_h();
	int get_m();
	int get_s();
	void get(int &hh,int &mm,int &ss) ;
	void set_h(int new_h);
	void set_m(int new_m);
	void set_s(int new_s);
	void set(int hh,int mm,int ss);
		private:
	int h,m,s;
};
int MyTime::get_h() {return h;}
int MyTime::get_m() {return m;}
int MyTime::get_s() {return s;}
void MyTime::get(int &hh,int &mm,int &ss) {
	hh=h;	mm=m;	ss=s;
}
void MyTime::set_h(int new_h) {h=new_h%24;}
void MyTime::set_m(int new_m) {m=new_m%60;}
void MyTime::set_s(int new_s) {s=new_s%60;}
void MyTime::set(int hh,int mm,int ss) {
	set_h(hh); set_m(mm); set_s(ss);
}
int main() {
	MyTime t;
	int i=0,j=0,k=0;
	t.set(25,15,0); 
	t.get(i,j,k);
	cout << i << ':' << j << ':' << k <// access to member data(property) by a method
	cout <<"myobj.i="<return 0;
}
ويجوز أن يكون تعريف الصنف بعد استعماله! ولكن لتحقيق ذلك يجب أن تعلن أولاً عن وجود صنف بهذا الاسم دون تحيدده كما يلي
#include
class MyTime; // declare the class if it's defined later
int main() {
	MyTime t;
	int i=0,j=0,k=0;
	t.set(25,15,0); 
	t.get(i,j,k);
	cout << i << ':' << j << ':' << k <// access to member data(property) by a method
	cout <<"myobj.i="<return 0;
}
class MyTime {
		public:
	int get_h();
	int get_m();
	int get_s();
	void get(int &hh,int &mm,int &ss) ;
	void set_h(int new_h);
	void set_m(int new_m);
	void set_s(int new_s);
	void set(int hh,int mm,int ss);
		private:
	int h,m,s;
};
int MyTime::get_h() {return h;}
int MyTime::get_m() {return m;}
int MyTime::get_s() {return s;}
void MyTime::get(int &hh,int &mm,int &ss) {
	hh=h;	mm=m;	ss=s;
}
void MyTime::set_h(int new_h) {h=new_h%24;}
void MyTime::set_m(int new_m) {m=new_m%60;}
void MyTime::set_s(int new_s) {s=new_s%60;}
void MyTime::set(int hh,int mm,int ss) {
	set_h(hh); set_m(mm); set_s(ss);
}


7.3.5 المؤشر الذاتي

لو أن وظيفة main في المثال السابق كانت كما يلي
int main() {
	MyTime t1,t2;
	t1.set_h(1); 
	t2.set_h(2); 
	cout << t1.get_h() << endl;
	cout << t2.get_h() << endl;
	return 0;
}
لاحظ أن t1.set_h(1) جعلت قيمة t1.h=1 و t2.set_h(2) جعلت قيمة t2.h=2 وكما هو متوقع ناتج تنفيذ البرنامج
1
2
فإذا دققنا في الاستدعاء t1.get_h() والذي استخدم لطباعة الواحد كانت عبارة عن return h; وقيمة h هنا هي 1 في الاستدعاء الثاني t1.get_h() الذي نتج عنه 2 فقد كان أيضاً return h; وقيمة h هي 2 . لحظة واحدة أليست h واحد أم أنها 2 ؟ كيف يكون لها قيمتان؟. هنا يجب أن ننتبه إلى أن أي كائن مثل t1 و t2 كل منها يَشغل موقع مختلف من الذاكرة، بالتالي في كل منهما نجد h و m و s مختلفة عن الأخرى لنميزه نقول h الخاصة ب t1 ونعبر عن ذلك ب t1.h خارج متن الصنف. هذا يجيب عن السؤال الخاص بكيف أمكن لمتغير أن يحمل قيمتان ظاهرياً ولكن كيف تمييز الوظيفة أن المتغير h عائدة للكائن الفلاني ونحن لم نحدد في متن الوظيفة ما يشير إلى أي كائن بل كات مجرد h ؟ جواب هذا السؤال يكمن في وجود متغير عضو في الصنف أنت لم تطلبه اسمه this وهو مؤشر من الصنف نفسه أي في مثالناMyTime *this; يتم إضافة هذا تلقاياً لكل أعضاء الصنف أي أن الوظيفة t1.get_h() تكافئ الوظيفة التالية ويمكن إعادة كتابتها لتصبح
int MyTime::get_h() {return this->h;}
ويتم تحديد قيمة هذا المؤشر بتمريره (داخلياً ودون تدخل منك) كمعامل أول للوظيفة أي أن الاستدعاء t1.get_h(); في سي++ يقابله t1.get_h(&t1); ونموذج هذه الوظيفة في سي++ وسي على الترتيب هو
int MyTime::get_h();
int get_h(MyTime *this);
ولأن العملية تتم تلقائياً فإن الحاجة لاستخدام المؤشر الذاتي نادرة إذا أن الكود this->h يمكن أن يستبدل ب h لهذا فإن الاستعمال المفضل لها هو عند الحاجة لإعادة مؤشر pointer أو مرجع(اسم آخر) reference للكائن نفسه فإنت لا تستطيع أن تكتب return t1; لأنها غير معروفة هناك بل تكتب return this; أو return *this; عدل الوظائف السابقة لتصبح
MyTime& MyTime::set_h(int hh) {
	h=hh%24; return *this;
}
MyTime& MyTime::set_m(int mm) {
	m=mm%60; return *this;
}
MyTime& MyTime::set_s(int ss) {
	s=ss%60; return *this;
}
هذه الوظائف تعيد قيمة المؤشر الذاتي أي أنها تعيد الكائن مما يسمح لكل بكتابة
int main() {
	MyTime t1,t2;
	int i=0,j=0,k=0;
	t1.set_h(1).set_m(30).set_s(0); 
	t2.set_h(2).set_m(45); 
	t1.get(i,j,k);
	cout << i << ':' << j << ':' << k <return 0;
}


7.3.6 وظيفة الإنشاء

لا يجوز إعطاء قيم استهلالية للخصائص مباشرة بعملية الإحلالint h=0; وللقيام بهذا نستعمل وظيفة خاصة نسميها constucter والهدف منها معالجة إنشاء كائن جديد من صنف معين للقيام بالعمليات الإستهلالية مثلاً حجز الذاكرة أو تصفير المتغيرات أو وضع قيمة لمتغير مثل isReady لتساوي TRUE حيث يستدع مصنف سي++ هذه الوظيفة تلقائياً عند تعريف كائن جديد أو عند حجزه باستعمال الكلمة المفتاحية new
 تحذيرالعبارة التالية خطأ int i; i(0); لأن وظيفة الإنشاء constucter .تستدعى عند التعريف فقط
تحمل هذه الوظيفة نفس اسم الصنف ولا تعيد أي شيء ولا حتى من ونوع "بلا" void ويمكن أن تأخذ أي عدد من المعاملات ويمكن أن يوجد أكثر من واحد منها
MyTime::MyTime(int hh) {
	h=m=s=0;
}
MyTime::MyTime(int hh,int mm,int ss=0) {
	h=hh%24; m=mm%60; s=ss%60;
}
ومن الطبيعي أن تسأل أي معاملات ونحن لا نستدعِ هذه الوظيفة بل سي++ تستدعيها خفية عني وأنا لا أحدد أي معامل؟ لاحظ أن هذا ينطبق على سي وليس على سي++ إذ يمكنك تمرير معاملات عد تعريف متغير لاحظ هذا الكود
#include
int main() {
	int i(5);  // pass an argument to (int) constructer
	cout << i <
وبنفس الطريقة يمكن أن تكون وظيفة main في برنامجنا
// ...
int main() {
	int i=0,j=0,k=0;
	MyTime t1; // it sets h=m=s=0 by calling the 1st constructer
	MyTime t2(1,30); // it sets h=1,m=30,s=0 by calling the 2nd constructer

	t1.get(i,j,k);
	cout << i << ':' << j << ':' << k <// ...
أي أنك تمرر المعاملات من خلال أقواس بعد اسم الكائن عند لتعريف ويجب أن تطابق هذه أحد نماذج وظائف الإنشاء، في حال عدم وضع الأقواس فإننا هنا نستدعي وظيفة الإنشاء التي لا تحتوي معاملات ويجوز وضع قوسين فارغين بكل اختياري. إذا كان الصنف بلا وظيفة إنشاء كما في الصنوف في بداية هذا الفصل فإن سي++ تزودنا بوظيفة إنشاء تلقائية لا تقوم بشيء.

7.3.7 وظيفة الإنشاء والنسخ

عند تعريف متغير بالشكل التالي int i=j; فإن العملية هنا ليست عملية إحلال أي ليست حساب قيمة ما على اليمين ووضع قيمتها في ما على اليسار، لأن جملة التعريف كما قلنا تحتوي عمليات استهلالية وحجز ذاكرة ، لذلك تعتبر هذه هي عملية نسخ محتويات وهي عملياً لن تختلف في حالتنا لو اعتبرناها عملية احلال. ولكن في حالة الكائنات التي لا يفترض بمصنف سي++ أن يفترض أي شيء عن طبيعة البيانات. لأخذ على سبيل المثال صنف يمثل منظومة يتم حجزها ديناميكياً ويتم الإحتفاظ بمؤشر على موقعها. عند تعريف كائن جديد ليكون نسخة عن كائن آخر فإننا هنا أمام طريقتين للنسخ: الأولى أن يتم وضع قيم كل الخصائص في الأول لتكون مساوية للثاني هنا فإننا نجعل المؤشر في الأول يساوي العنوان الذي يشير إليه الآخر أي أن المؤشرين يشيران لنفس المنطقة من الذاكرة. والثانية هي حجز منطقة جديدة مختلفة والإحتفاض بعنوانها ونسخ محتويات المنطقة الثانية إليها. الفرق بين الطريقتين هو أنه في الأولى أي تعديل على منطقة الذاكرة لأحد الكائنات يؤثر على الآخر بسبب وجود منطقة واحد والكائنين يشيران لها أما الأخرى فهاك منطقتين في الذاكرة تحتويان على نفس البيانات لكن أي تعديل في أي منهما لن يؤثر على الآخر.
إن الطريقة الأولى(نسخ الخصائص دون الإهتمام لمحتوياتها) هي التلقائية وهي المفضلة عندما لا يحتوي الصنف على مؤشرات ومراجع وعدما لا يكون كل كائن من هذا الصنف مرتبط بأي كائن آخر بعلاقات معينة ولكن في تلك الحال الخاصة يصبح من الضروري الانتباه وتعريف وظيفة إنشاء ونسخ خاصة تعالج تلك الحالات نسميها copy constructor.
وهي عبارة عن وظيفة إنشاء عادية تأخذ معامل وحيد هو مرجع لكائن من نفس النوع . هذا المثال يوضح ذلك: لنفرض أن صنف يمثل منظومة يحتوي على n عدد العناصر و ptr مؤشر لمنطقة من الذاكرة هذا الجزء من الكود يوضح الفكرة
#include
#include // for memcpy
// ...
class MyArray {
		public:
	MyArray(int N) {
		n=N;
		if (!ptr=new char[n]) n=0;
	}
	// The Copy Constructer
	MyArray(MyArray &A1) {
		n=A1.n;
		if (!ptr=new char[n]) n=0;
		else memcpy(ptr,A1.ptr,n);
	}
	int get_n() {return n;}
	char *get(int off) {
		if (off<0 || off>=n) return NULL:
		return ptr+off;
	}
		private:
	int n;
	char *ptr;
};
// ...


7.3.8 وظيفة الهدم

من الإزعاجات الكثيرة التي تسببها المؤشرات والتي على المبرمج الإنتباه إليها هي أن المنطقة المحجوزة ديناميكياً بواسطة new أو malloc تكون من عمر البرنامج ككل أي أن مهمة تحريرها في اللحظة المناسبة تعود على المبرمج وقد يتم تعريف كائن داخل وظيفة ثم إزالته عند الخروج منها والمناطق التي حجزها لا تزال محجوزة. أيضا من السناريوهات التي على المبرمج الإنتباه إليها هي فتح الملفات أو الأجهزة وغيرها من مصادر الجهاز هنا أيضا يجب أن إغلاق وتحرير تلك المصادر في الوقت المناسب.
لهذه الغاية توجد وظيفة تستدعى تلقائياً وضمنياً فقط عند الحاجة لإلغاء كائن مثلاً عند الخروج من الوظيفة التي حجزته في المكدس أو عد استعمال الكلمة المفتاحية delete للكائنات المحجوزة بواسطة new. هذه الوظيفة تأخذ اسم الصنف مسبووق بعلامة ~ وهي لا تعيد نتيجة كما في ظيفة الإنشاء، ولا تأخذ معاملات.
// ...
class MyArray {
		public:
	MyArray(int N) {
		n=N;
		if (!ptr=new char[n]) n=0;
	}
	// destructer
	~MyArray() {
		// deallocate memory
		delete [] ptr;
	}
	// ...
		private:
	int n;
	char *ptr;
};
// ...
مثال آخر للتوضيح
#include
int my_class_n=0; // global variable to count them
class MyClass {
		public:
	// constructer
	MyClass(char *Name) {
		n=++my_class_n;
		name=Name;
		cout << "Create the "
			<"-th Class, it's name: "
			<// destructer
	~MyClass() {
		cout << "Delete the "
			<"-th Class, it's name: "
			<private:
	int n;
	char *name;
};
// do-nothing function
void foobar() {
	cout << "we are in foobar"<"[foobar's a1]");
	MyClass b1("[foobar's b1]");
	MyClass *c1=new MyClass("[foobar's c1 allocated by new]");
	cout << "getting out of foobar"<"[global a1]");
int main() {
	cout << "we are in main"<"[main's a1]");
	MyClass b1("[main's b1]");
	MyClass *c1=new MyClass("[main's c1 allocated by new]");
	cout << "Calling foobar"<"getback main"<"[main's d1 allocated by new]");
	cout << "getting out of main"<


7.3.9 الكائن الثابت

يمكن للمبرمج أن يلزم نفسه بأن لا يعدل متغير بعد انشاؤه كما لاحظنا في const int i=5; وهذا ينطبق على الكائنات const MyTime t(5,30); وفي الكائنات الثابتة تصبح ممنوعاً من تعديل قيم حتى العناصر العامة t.h=6; وكذلك كل الوظائف التي تعلن أنها قد تعدل على الكائن أما الوظائف التي يسمح لك بتمرير الكائن إليها هي التي تستقبل كائن ثابت في مثالنا void foobar(const MyTime arg1); أو مؤشر أو مرجع له void foobar(const MyTime &arg1); وحتى الوظائف الأعضاء (الطرق) يجب أن تعلن أنها لن تعدل على الكائن وذلك بكتابة const بعد قوس المعاملات في النموذج prototype وفي متن الوظيفة وقبل الفاصلة المنقوطة . لهذا يكون الصنف MyTime الذي عرفناه سابقاً لا يصلح لأن الوظيفة get وغيرها من الوظائف التي لا تعدل ليست معلنة أنها لا تعدل لذا يجب أن تتصبح كما يلي
#include
class MyTime {
		public:
	MyTime(int hh);
	MyTime(int hh,int mm,int ss=0);
	int get_h() const;
	int get_m() const;
	int get_s() const;
	void get(int &hh,int &mm,int &ss)  const;
	void set_h(int new_h);
	void set_m(int new_m);
	void set_s(int new_s);
	void set(int hh,int mm,int ss);
		private:
	int h,m,s;
};
MyTime::MyTime(int hh) {
	h=m=s=0;
}
MyTime::MyTime(int hh,int mm,int ss=0) {
	h=hh%24; m=mm%60; s=ss%60;
}
int MyTime::get_h() const {return h;}
int MyTime::get_m() const {return m;}
int MyTime::get_s() const {return s;}
void MyTime::get(int &hh,int &mm,int &ss) const {
	hh=h;	mm=m;	ss=s;
}
void MyTime::set_h(int new_h) {h=new_h%24;}
void MyTime::set_m(int new_m) {m=new_m%60;}
void MyTime::set_s(int new_s) {s=new_s%60;}
void MyTime::set(int hh,int mm,int ss) {
	set_h(hh); set_m(mm); set_s(ss);
}
ويجب أن تنتبه إلى أن وظيفة الإنشاء أو الهدم يسمح بتفيذها حتى لو كان الكائن ثابتاً. ولوضع قيمة استهلالية في الكائنات الثابتة داخل وظيفة الإنشاء فإننا لا نقوم بذلك بعملية الإحلال = بل باستدعاء وظيفة الإنشاء الخاص به كما يلي
class MyClass {
		public:
	MyClass(const int value,const MyTime t0);
		private:
	const int v;
	const MyTime t;
};
MyClass::MyClass(const int value,const MyTime t0) 
	: v(value), t(t0) {
	cout << v; 
}


7.3.10 أعضاء الكائن الستاتيكية

الخصائص الستاتيكية في صنف هي تلك التي يمكن الوصول لها دون كائن! أي أن نسخة منها مشتركة بين كل الكائنات وحتى قبل حجز أي كائن فهي مثل المتغيرات الستاتيكية والعامة. ويكون ذلك بذكر نوع الخاصية ثم الصنف الذي تعود له ثم :: ثم اسم الخاصية. أما الطرق الستتاتيكية فهي تتلك التي لا تصل إلى للخصائص الستاتيكية أي يمكن استدعاؤها دون كائن أيضاً وتكون بوضع static قبل ; في النموذج أو قبل { في متن الوظيفة.
class MyClass {
		public:
	static int a;
	MyClass(const int vv);
	int get_a() static {return a;}
		private:
	const int v;
};
MyClass::MyClass(const int vv) 
	: v(vv) {
	cout << v; 
}
// ....
int MyClass::a=0; // we can access to static member if it's public
int c=MyClass::get_a();


7.3.11 الصداقة بين الصنوف

يمكن أن نقول أن صنفاً ما له صديق من الصنف الفلاني أو من الوظيفة الفلانية بحيث يسمح لهم بالوصول لجميع الخصائص والطرق (حتى لو لم تكن عامة) ويكون ذلك بكتابة الكلمة المفتاحية friend ثم اسم الصنف الصديق أو نموذج الوظيفة الصديقة في أي مكان داخل متن الصنف كما يلي
class MyClass {
	friend MyOtherClass;
	friend void foobar(MyClass *c);
		public:
	MyClass(const int vv);
		private:
	const int v;
};
MyClass::MyClass(const int vv) 
	: v(vv) {
	cout << v; 
}
void foobar(MyClass *c) {
	cout << c->v; // here we access to private data
}


7.3.12 إعادة تعريف العمليات

العمليات مثل + و - و * و / و << و >> يمكن إعادة تعريفها لتعمل مع الصنف الجديد مثلاً يمكنك أن تعرف عملية جمع أو طرح بين زمنين MyTime أو بين MyTime وعدد صحيح يمثل الثواني وتكون العملية عبارة عن وظيفة عادية اسمها operator ثم رمز العملية وتأخذ معامل أو اثنين مثلاً MyTime operator+(const MyTime t1,const MyTime t2); ويمكن أن تكون عضو في الصنف بحيث يكون المعامل الأول هو المؤشر الذاتي مثلاً MyTime MyTime::operator+(const MyTime t2); ويمكن إعادة تعريف كل العمليات عدا . .* ?: sizeof
class MyClass {
		public:
	MyClass(const int vv): v(vv) { }
	MyClass operator+(const MyClass c2) {
		MyClass tmp(v+c2.v);
		return tmp;
	}
	MyClass operator=(const int v2) {
		set(v2);
		return *this;
	}
	MyClass operator=(const MyClass c2) {
		set(c2.v);
		return *this;
	}
	int operator==(const MyClass c2) {
		if (c2.v==v) return -1;
		else return 0;
	}
	int get() const {return v;}
	void set(int vv;) {v=vv;}
		private:
	int v;
};
ويمكن استدعاء هذه الوظائف بالطريقة العادية للعمليات مثلاً c3=c1+c2; أو بالطريقة العادية لاستدعاء الوظائف c3=c1.operator+(c2); ومن بين العمليات عملية الأقواس التي تأخذ أي عدد من المعاملات أو عملية [] التي يمكن أن تستعمل لتطبيق المنظومات وهي تأخذ معامل وحيد غالباً رقم.
وللتمييز بين العمليات القبلية والبعدية مثل a++; ++a; نضيف للبعدية معامل زائف (أي لن يتم تمريره) كما يلي
class MyClass {
		public:
	MyClass(const int vv): v(vv) { }
	// pre-increment (++X)
	MyClass& operator++() {
		++v;
		return *this;
	}
	// post-increment (X++)
	MyClass operator++(int dummy) {
		MyClass tmp(v);
		++v;
		return tmp;
	}
	int get() const {return v;}
	void set(int vv;) {v=vv;}
		private:
	int v;
};


7.3.13 وظائف تحويل الصنف

كما كنا فعل بالمتغيرات العادية أحياناً بتحويل العدد الصحيح إلى نسبي وذلك بوضع النوع المراد التحويل له بين قوسين كما في int i=5; float f=(int)i; يمكنك أيضاً أن تعمل عملية تحويل بين الصف الجديد وصنوف أخرى أو أنواع سي++ الأساسية وذلك بتعريف وظيفة عضو تقوم بالتحويل المطلوب ويكون اسمها operator ثم النوع المراد التحويل له ولكن هنا لا نحدد نوع الناتج لأنه معروف
class MyClass {
		public:
	MyClass(const int vv): v(vv) { }
	// convert to integer
	operator int() {
		return v;
	}
	int get() const {return v;}
	void set(int vv;) {v=vv;}
		private:
	int v;
};


7.3.14 الوراثة

نتحدث عن الوراثة هنا بعيداً عن مندل، وما يهما في الموضوع هو إذا كنا قد حصلنا على صنف بتصميم كامل وأردنا تطوير صنف آخر منه بحيث يشترك معه في بعض الخصائص والطرق مما يوفر علينا ساعات من التطوير. لنفرض أن لدينا صنف يمثل مستطيل له طول وعرض ونريد توسيع المفهوم ليصبح زر أو لدينا صنف يمثل عامل ونريد عمل صنف جديد ليكون مدير ولأن المدير لا يختلف كثيراً عن العامل فإننا نشتقه منه دون كتابة الكثير من الكود.
نسمي الصنف الجديد بالصنف المشتق derived class والذي اشتق منه نسميه الصنف القاعدة base class ويمكن اشتقاق صنف من أكثر من قاعدة.
هناك ثلاث طرق اشتقاق عام ومحمي وخاص وأكثرها شيوعاً هو الإشتقاق العام public inheritance فإذا كان الصنف الأصلي يمتلك خاصية عامة أو طريقة عامة فإن الصنف المشتق سيحتويها تلقائياً(أي تكون مرئية)، ولكن إذا كانت الخاصية أو الطريقة خاصة فإنها تصبح غير مرئية في الصنف المشتق، لهذا يوجد نوع جديد من المحددات هو العضو المحمي protected وهو أكثر تشدداً من العام public فلا يسمح للوصول إليه من خارج متن الصنف (أو من الصنوف المشتقة والصديقة والوظائف الصديقة) وهو أقل تشدداً من الخاص private لأن الأخير لا يظهر في الصنوف المشتقة.
أما النوعين الآخرين للإشتقاق فيزداد التشديد فيها ففي الإشتقاق المحمي تصبح الأعضاء العامة والمحمية محمية والخاصة تختفي أما الإشتقاق الخاص فتختفي فيه كل الأعضاء.

محدد نوع العضوالإشتقاق العامالإشتقاق المحميالإشتقاق الخاص
العضو العامعاممحميخاص
العضو المحميمحميمحميخاص
العضو الخاصمخفيمخفيمخفي
إقرأ الجدول على شكل أعمدة مثلاً في الإشتقاق العام تكون الأعضاء العامة للصنف القاعدة أعضاءً عامة في الصنف المشتق والمحمية تصبح محمية والخاصة تصبح غير مرئية!
ويكون الإشتقاق بوضع : بعد اسم الصنف ثم نوع الإشتقاق ثم اسماء القواعد تفصل بينها فاصلة كما يلي
class MyClassA {
		public:
	MyClassA(int A) {a=A;}
	int A() const {return a;}
	int get() const {return a;}
	void set(int A) {a=A;}
	void A(int A) {a=A;}
		protected:
	int a;
}
class MyClassB {
		public:
	MyClassB(int B) {b=B;}
	int B() const {return b;}
	int get() const {return b;}
	void set(int B) {b=B;}
	void B(int B) {b=B;}
		protected:
	int b;
}
class MyClass: public MyClassA,MyClassB {
		public:
	MyClass(int A,int B,int C)
		: MyClassA(A),MyClassB(B) { c=C;}
	int get() const {return c;}
	void foobar() {
		int i,j,k;
		i=A(); // call MyClassA::A() directly
		j=B(); // call MyClassB::B() directly
		i=MyClassA::get(); // call MyClassA::get() because it's overridden
		j=MyClassA::get(); // call MyClassB::get() because it's overridden
		k=get();// call MyClass::get()
		cout <

ويجوز للصنف المشتق أن يعيد تعريف عضو موجود لدى القاعدة مثلاً عند اشتقاق متوازي مستطيلات من مستطيل وكان المستطيل يحتوي طريقة لحساب المساحة فإننا لا نريد هذه المساحة بل نريد المساحة الجانبية لهذا نكتب طريقة بنفس اسم طريقة المساحة وعند طلب المساحة لمتوازي المستطيلات فإن التي ستستدعى هي الصحيحة وأما الأخرى لاستدعائها نكتب اسم القاعدة ثم :: قبل الوظيفة لتحديد أننا نريد النسخة القديمة من الطريقة.
نسمي الصنف الذي لا يوجد منه كائنات (بل يستخدم لاشتقاق صنوف أخرى) بالصنف المجرد/التجريدي abstract وحدده بوضع وظيفة الإنشاء خارج حدود المحدد العام للأعضاء بهذا لا يمكن استدعاء وظيفة الإنشاء إلا بشكل داخلي.

7.3.15 الوظائف الوهمية

تسمح لك سي++ بإعلان وظائف أعضاء دون تعريفها وترك تعريفها منوطاً بالصنف المشتق، تسمى هكذا وظائف بالوظائف الوهمية أو الإفتراضية virtual ولعمل مثل هذه الوظائف نكتب virtual قبل نموذج الوظيفة.
class Base1 {
		public:
	// ...
	virtual void doit();
	// ...
}
class Derived1 : public Base1 {
		public:
	// ...
	void doit() {
		//...
		how_to_do_it_goes_here();
		//...		
	}
	// ...
}


وعند استدعاء هذه الوظيفة من كائن من صنف مشتق من صنف مشتق من صنف آخر وفي كل منهما نسخة مختلفة من هذه الوظيفة فإن الوظيفة التي تنفذ هي تلك العائدة للصنف الأخير حتى لو قمنا بالتلاعب بنوع الصنف لأن الصنف يحتوي مؤشر على جدول الوظائف الوهمية
class Base1 {
		public:
	void print() {
		cout <<"I'm the base1\n";
	}
	virtual void vprint() {
		cout <<"I'm the base1\n";
	}

}
class Base2 : public Base1 {
		public:
	void print() {
		cout <<"I'm the base2\n";
	}
	void vprint() {
		cout <<"I'm the base2\n";
	}
}
class Base3 : public Base2 {
		public:
	void print() {
		cout <<"I'm the base3\n";
	}
	void vprint() {
		cout <<"I'm the base3\n";
	}
}
// ...
int main() {
	Base3 b3;
	Base1 *b=(Base1 *)&b3;
	// b is a pointer to a Base3 class
	// but the data type declared is for Base1
	b->print(); // non-virtual function get fooled
	b->vprint(); // virtual function print the true message
}




منقول من"http://cltb.ojuba.org"

0 التعليقات: