الترميز في ++C
يتضمن هذا المقال طريقتين جديدتين نسبيا للإقتراب من ساحة برمجة السي++ . فإمكانية التعريف الرمزي للبيانات تسمح لنا أن نصوغ دوالا وفئات تتعامل مع أكثر من نوع من البيانات دون حاجة لتكرار الصياغة . وألية الاستثناءات تعطي طريقة موحدة وميسرة للتعامل مع الأخطاء التي تحدث داخل الفئات ولم يكن ترميز البيانات أو الاستثناءات من أساسيات لغة السي الأصلية ولكنها أدخلت بها منذ 1990 وأصبحت معتمدة في المواصفات الخاصة بها من قبل هيئتي الأنسي والأيزو . وتدعم بورلاند سي++ الأسلوبين , أما التربو سي++ فلا يدعم سوي أسلوب الترميز .
القسم الأول : الترميز
الدوال المرمزة
لنفرض أن لدينا الدالة التالية , وهي تعيد القيمة المطلقة لأي عدد صحيح (بمعني أنها تعيد القيمة العددية , بصرف النظر عن الإشارة فتعيد كلا من القيمتين 5 و -5 بقيمة 5 ):
Int abs(int n)
{
Retutn (n<0) ? –n: n;
}
] تفسير المعامل الشرطي المستخدم : إذا كانت n أكبر من الصفر (موجبة) أعد قيمتها , وإن كانت أقل من الصفر (سالبة) , أعد –n أي تحول للموجب , حيث إن سالب السالب هو موجب , وبذلك فإن المعاد تكون القيمة الموجبة سواء أكان المتغير موجبا أو سالبا [
والدالة كما تري تعمل علي الأعداد الصحيحة . ولتنفيذ نفس الوظيفة علي نوع أخر , وليكن float . نحتاج لدالة ثانية , تماثل نفس الدالة في أوامرها وصياغتها , ولكن بتعريف مغاير للمتغيرات , ثم ثالثة لنوع ثالث , وهكذا نحتاج لعدد من الدوال بقدر الأنواع المطلوبة , رغم كون صياغة الدالة في حد ذاته لا يتغير .
ويعطينا أسلوب زيادة تحميل الدوال إمكانية استخدام نفس الاسم لكل هذه الدوال , ولكن لا يحل المشكلة الأصلية , وهو ضرورة صياغة دالة لكل نوع .
حبذا لو أوتينا وسيلة تمكننا من تطبيق نفس الصياغة علي أكثر من نوع , هنا تكمن ميزة المرونة التي يعطيها أسلوب التعريف الرمزي للبيانات , وذلك باستخدام الكلمة الحاكمة template أمام أسم نوع البيان , والتي تعني أن النوع المذكور هو مجرد رمز يمكن أن يترجم لأي نوع من أنواع البيانات
وإليك مثالا تطبيقيا لهذا الأسلوب :
Tempabs.cpp
// tempabs.cpp
// template used for absolute value function
#include <iostream.h>
#include <conio.h>
template <class T> // function template
T abs(T n)
{
return (n < 0) ? -n : n;
}
void main()
{
int int1 = 5;
int int2 = -6;
long lon1 = 70000L;
long lon2 = -80000L;
double dub1 = 9.95;
double dub2 = -10.15;
// calls instantiate functions
cout << "\nabs(" << int1 << ")=" << abs(int1); // abs(int)
cout << "\nabs(" << int2 << ")=" << abs(int2); // abs(int)
cout << "\nabs(" << lon1 << ")=" << abs(lon1); // abs(long)
cout << "\nabs(" << lon2 << ")=" << abs(lon2); // abs(long)
cout << "\nabs(" << dub1 << ")=" << abs(dub1); // abs(double)
cout << "\nabs(" << dub2 << ")=" << abs(dub2); // abs(double)
getche();
}
وإليك الخرج المتوقع من البرنامج :
Abs(5)=5
Abs(-5)=5
وهكذا لبقية القيم . وتري من ذلك أن الدالة قد عملت مع كافة الأنواع بنفس الصياغة . وهي سوف تعمل علي أي نوع أخر , كالعدد الصحيح أو المحرفي , بل وسوف تعمل علي الأنواع الموضوعة بمعرفة المبرمج .
وكما تري , فإن التعريف الرمزي لنوع البيان كان علي الصورة :
Template <class T>
T abs(Tn)
{
Return (n<0) ? –n:n;
}
حيث بدأ التعريف بكلمة حاكمة هي template فكيف مثل هذا التعريف هذه الإمكانية المدهشة ؟
صياغة رمز النوع
إن الحرف T في التعريف السابق ليس أكثر من رمز , وكان يمكن استخدام أي رمز أخر , وليكن anyType . وكلمة template تخبر محول الصياغة أننا بصدد وضع رمز لأنواع البيانات , يعوض عنه بالنوع الذي تختاره الدالة الأصلية للمتغيرات المستخدمة . وعلي ذلك تسمي الدالة التي تستخدم هذا الرمز في صياغتها "الدالة المرمزة" وهي تكون في الواقع مجرد نمط للدالة الحقيقية التي سوف يصوغ محول الصياغة الكود الخاص بها , ولهذا السبب فهي تسمي ''function template'' أي نمط دالة (أو دالة نمطية ) ,كما يسمي المعامل الذي يستخدم له التعريف الرمزي "المعامل المرمز template argument " .
ما يفعله محول الصياغة
ما الذي يفعله محول الصياغة حين يقابل تعريفا رمزيا ؟ لا شئ البتة ؛ ليس أكثر من تذكره دون أن ينتج أيه أكواد , حيث لا علم لديه بما ستكون عليه أنواع المتغيرات بعد . ولا يتم إنتاج الكود إلا بعد استدعاء الدالة المرمزة بالفعل , بأمر مثل : abs(int1) , حينئذ يتحدد نوع المتغير , ويمكن لمحول الصياغة أن يقوم بعمله في إنتاج الشكل المناسب من الدالة ليتفق مع النوع المحدد , والذي فيه يعوض عن الحرف T(الرمز) بالنوع int . وهكذا لبقية الأنواع . فبدلا من أن نضطر لكتابة ثلاثة صيغ (أو أكثر) لنفس الدالة يعفيك محول الصياغة من هذا العناء , فهو يضع ثلاث صياغات هدفية مختلفة , مستخدما التعريف الرمزي كدليل له . تسمي عملية إنتاج الدوال والفئات المرمزة استحداث inistantiation , علي أساس أن كل دالة أو فئة منتجة هي " حادثة instance من التعريف العام (راجع مستحدثة في الفصل الأول)
تبسيط الصياغة
عليك ملاحظة أن القدر الذي يشغله كود الهدف للبرنامج ف يذاكرة الرام لا يتغير في حالتي الإستفادة من ترميز البيانات أم لا , حيث سوف ينتج محول الصياغة نفس العدد من الأكواد الهدفية في الحالتين , ولكن الذي كسبناه هو تبسيط صياغة البرنامج , حين أكتفينا بصياغة مصدرية واحدة , وهو مكسب وليس بالقليل ومن جهة أخري , فإن بإمكانك تغيير أسلوب عمل الدالة بأقل قدر من المجهود في تغيير صياغة المصدر .
ولعلك لاحظت مدي إتفاق هذا الأسلوب مع المنهج العام للبرمجة الكائنية , والتي تعتمد كثيرا علي عمل الأنماط التي يمكن أن تطبق بأكثر من صورة , ألا تري أن الفئات هي مجرد نمط عام يمكن أن يأخذ أكثر من صورة لكائن ؟ كذلك فإن التعريف الرمزي هو نمط يصلح لأكثر من نوع .
دالة بأكثر من معامل مرمز
إليك دالة مرمزة تبحث عن قيم ما داخل مصفوفة , وتعيد دليل العنصر المحتوي عليها أو -1 إذا لم يكن بالمصوفة عنصر يحمل القيمة التي يبحث عنها . وحتي يمكن تطبيق هذه الوظيفة لأي نوع من أنواع المتغيرات , سوف نستخدم أسلوب الترميز .
Tempfind.cpp
// tempfind.cpp
// template used for function that finds number in array
#include <iostream.h>
#include <conio.h>
// function returns index number of item, or -1 if not found
template <class atype>
int find(atype* array, atype value, int size)
{
for(int j=0; j<size; j++)
if(array[j]==value)
return j;
return -1;
}
char chrArr[] = {1, 3, 5, 9, 11, 13}; // array
char ch = 5; // value to find
int intArr[] = {1, 3, 5, 9, 11, 13};
int in = 6;
long lonArr[] = {1L, 3L, 5L, 9L, 11L, 13L};
long lo = 11L;
double dubArr[] = {1.0, 3.0, 5.0, 9.0, 11.0, 13.0};
double db = 4.0;
void main()
{
cout << "\n 5 in chrArray: index=" << find(chrArr, ch, 6);
cout << "\n 6 in intArray: index=" << find(intArr, in, 6);
cout << "\n11 in lonArray: index=" << find(lonArr, lo, 6);
cout << "\n 4 in dubArray: index=" << find(dubArr, db, 6);
getch();
}
في هذا المثال أستخدمنا للتعريف الرمزي الرمز atype , وهو يظهر في معاملين مرمزين للدالة atype* array, atype value : .
وإليك خرج البرنامج :
5 in charArray index=2
6 in intArray index=1
11 in lonArray index=4
4 in dubArray index=1
وقد أنتج محول الصياغة أربعة صياغات مختلفة من الدالة , واحدة لكل نوع استدعيت الدالة المرمزة به .
المعاملات المرمزة يجب أن تتوافق
ليس بإمكان محول الصياغة أن ينتج كودا لدالة معاملات عرف النوع الرمزي فيها بصور مختلفة , ففي مثالنا السابق لا يمكن تعرف معاملا للدالة find علي أنه int والمعامل الثاني علي أنه float , والسبب واضح , فمحول الصياغة يصوغ كود الهدف بناء علي كون الرمز مقابل لنوع معين .
أكثر من رمز للبيانات
علي أنه يمكنك في التعريف الرمزي أن تستخدم أكثر من رمز , فبإمكانك مثلا استخدام تعريف كهذا :
Template < class atype, class btype>
ويمكنك استغلال ميزه كهذه في برنامجنا السابق , ففي الدالة find() قد لا تعرف مسبقا حجم المصفوفة, فإن كانت كبيرة قد تحتاج لأن تعرف حجمها علي أنه من النوع الطويل long size , يمكنك أن تعرف الحم في هذه الحالة تعريفا رمزيا باستخدام الرمز btype , ويعدل البرنامج بالصورة التالية :
Template < class atype, class btype>
Btype find(atype*array, atype value, btype size)
{
For(btype= j=0p; j<size; j++)
If(array[j]==value
Return
Return (btype) -1;
}
وبإمكانك الأن استخدام أي نوع من البيانات في المصفوفة , حتي الأنواع التي تضعهما بمعرفتك , وسوف ينتج محول الصياغة نسخا ليس فقط للمصفوفات مختلفة النوع , بل ومختلفة الحجم أيضا .
وزيادة عدد المعاملات الرمزية يزيد من عدد النسخ المولدة من الدالة المؤمزة , ولكن من جهة أخري فالنسخ لا تنشأ إلا عند الاستدعاء الفعلي للدالة .
ابدأ بدوال عادية
إذا أردت استغلال إمكانية التعريف الرمزي , فينصح بأن تبدأ صياغة الدوال في صورتها العادية , أي معرفة البيانات , وبعد أن تصححها وتطمئن لسلامتها , يمكنك تحويل تعريف معاملاتها رمزيا .
وفي هذا الخصوص , فإن يجب عليك التأكد من أن المؤثرات المستخدمة في الدالة تعمل بالفعل علي الأنواع التي تستخدمها في الدالة فمثلا في الدالة find يستخدم المؤثر == للمقارنة بين البيانات فلو أن الدالة أريد بها مقارنة عبارات نصية , فلن يكون ذلك ممكنا , حيث إن مقارنة العبارات النصية يكون بالدالة strcmp() . (علي أنه يمكن استخدام الدالة لفئة عرفها المبرمج , إذا زيد بها تحميل المؤثر == ليعمل علي كائناتها) .
تعليقات
إرسال تعليق