مؤشر الدالة
مؤشر الدالة ، ويُسمى أيضًا مؤشر الروتين الفرعي أو مؤشر الإجراء ، هو مؤشر يُشير إلى كود قابل للتنفيذ، وليس إلى بيانات. يؤدي فك مرجعية مؤشر الدالة إلى الحصول على الدالة المشار إليها ، والتي يمكن استدعاؤها وتمرير الوسائط إليها تمامًا كما في استدعاء دالة عادي. يُعرف هذا الاستدعاء أيضًا بالاستدعاء "غير المباشر"، لأن الدالة تُستدعى بشكل غير مباشر عبر متغير بدلًا من استدعائها مباشرةً عبر مُعرّف أو عنوان ثابت.
تسمح مؤشرات الدوال بتنفيذ أجزاء مختلفة من التعليمات البرمجية أثناء وقت التشغيل. كما يمكن تمريرها إلى دالة لتمكين عمليات الاستدعاء .
تدعم لغات البرمجة من الجيل الثالث (مثل PL/I و COBOL و Fortran و dBASE و dBL و C ) ولغات البرمجة كائنية التوجه (مثل C++ و C# و D ) مؤشرات الدوال .
مؤشرات الدوال البسيطة
أبسط طريقة لتنفيذ مؤشر الدالة (أو الروتين الفرعي) هي استخدامه كمتغير يحتوي على عنوان الدالة داخل الذاكرة القابلة للتنفيذ. تُنفذ لغات الجيل الثالث القديمة مثل PL/I و COBOL ، بالإضافة إلى اللغات الحديثة مثل Pascal و C، مؤشرات الدوال بهذه الطريقة عمومًا. [ 3 ]
تم تقديم مؤشرات الدوال في الإصدار 9.0 من لغة C#delegate* ، على النحو التالي : [ 4 ]
مثال بلغة C
يوضح برنامج C التالي استخدام مؤشرين للدوال:
func1تأخذ هذه الدالة مُعاملًا واحدًا من نوع double (double) وتُرجع مُعاملًا آخر من نوع double، ويتم تعيينها لدالة تقوم بتحويل السنتيمترات إلى بوصات.func2تأخذ مؤشرًا إلى مصفوفة أحرف ثابتة بالإضافة إلى عدد صحيح وتعيد مؤشرًا إلى حرف، ويتم تعيينها إلى دالة معالجة سلسلة C التي تعيد مؤشرًا إلى أول ظهور لحرف معين في مصفوفة أحرف.
#include <stdio.h> #include <string.h>double cm_to_inches ( double cm ) { return cm / 2.54 ; }// "strchr" جزء من معالجة السلاسل النصية في لغة C (أي لا حاجة لتعريفها) // انظر https://en.wikipedia.org/wiki/C_string_handling#Functionsint main ( void ) { double ( * func1 )( double ) = cm_to_inches ; char * ( * func2 )( const char * , int ) = strchr ; printf ( "%f %s" , func1 ( 15.0 ), func2 ( "Wikipedia" , 'p' )); // يطبع "5.905512 pedia" return 0 ; }يستخدم البرنامج التالي مؤشر دالة لاستدعاء إحدى دالتين ( sinأو cos) بشكل غير مباشر من دالة أخرى ( ) ، لحساب تقريب لتكامل ريمانcompute_sum للدالة . يعمل البرنامج عن طريق استدعاء الدالة للدالة مرتين، مع تمرير مؤشر إلى دالة المكتبة في المرة الأولى، ومؤشر إلى الدالة في المرة الثانية. بدورها، تستدعي الدالة إحدى الدالتين بشكل غير مباشر عن طريق فك مرجعية مؤشر الدالة الخاص بها عدة مرات، وجمع القيم التي تُرجعها الدالة المستدعاة، ثم إرجاع المجموع الناتج. يُكتب المجموعان إلى المخرج القياسي بواسطة .maincompute_sumsincoscompute_sumfuncpmain
#include <math.h>#include <stdio.h>// دالة تأخذ مؤشر دالة كوسيطدالة حساب المجموع ( دالة ( * دالة )( دالة ), دالة lo , دالة hi ) {double sum = 0.0 ;// أضف القيم التي تُرجعها الدالة المشار إليها '*funcp'for ( int i = 0 ; i <= 100 ; i ++ ) {// استخدم مؤشر الدالة 'funcp' لاستدعاء الدالةdouble x = i / 100.0 * ( hi - lo ) + lo ;double y = funcp ( x );sum += y ;}أعد المجموع / 101.0 * ( الأعلى - الأدنى )؛}مربع مزدوج ( x مزدوج ) {أعد x * x ;}int main ( void ) {المجموع المزدوج ؛// استخدم دالة المكتبة القياسية 'sin()' كدالة مُشار إليهاsum = compute_sum ( sin , 0.0 , 1.0 );printf ( "sum(sin): %g \n " , sum );// استخدم دالة المكتبة القياسية 'cos()' كدالة مُشار إليهاsum = compute_sum ( cos , 0.0 , 1.0 );printf ( "sum(cos): %g \n " , sum );// استخدم الدالة المعرفة من قبل المستخدم 'square()' كدالة مُشار إليهاsum = compute_sum ( square , 0.0 , 1.0 );printf ( "sum(square): %g \n " , sum );أرجع 0 ؛}الدوال
تُشبه الدوال الوظيفية، أو كائنات الدوال، مؤشرات الدوال، ويمكن استخدامها بطرق مماثلة. الدالة الوظيفية هي كائن من نوع فئة يُنفذ عامل استدعاء الدالة ، مما يسمح باستخدام الكائن داخل التعبيرات باستخدام نفس صيغة استدعاء الدالة. تتميز الدوال الوظيفية بقوة أكبر من مؤشرات الدوال البسيطة، إذ يمكنها احتواء قيم بياناتها الخاصة، وتتيح للمبرمج محاكاة الإغلاقات . كما تُستخدم أيضًا كدوال رد نداء إذا لزم استخدام دالة عضو كدالة رد نداء. [ 5 ]
لا تدعم العديد من لغات البرمجة الكائنية "الخالصة" مؤشرات الدوال. مع ذلك، يمكن تطبيق آلية مشابهة في هذه اللغات باستخدام مراجع إلى واجهات تُعرّف دالة واحدة (دالة عضو). أما لغات سطر الأوامر مثل C# و Visual Basic .NET فتُطبّق مؤشرات دوال آمنة من حيث النوع باستخدام المفوضين .
في اللغات الأخرى التي تدعم الدوال من الدرجة الأولى ، تعتبر الدوال بمثابة بيانات، ويمكن تمريرها وإعادتها وإنشاؤها ديناميكيًا بشكل مباشر بواسطة دوال أخرى، مما يلغي الحاجة إلى مؤشرات الدوال.
قد يؤدي الاستخدام المكثف لمؤشرات الوظائف لاستدعاء الوظائف إلى تباطؤ الكود على المعالجات الحديثة، لأن متنبئ التفرع قد لا يتمكن من معرفة مكان التفرع إليه (يعتمد ذلك على قيمة مؤشر الوظيفة في وقت التشغيل) على الرغم من أن هذا التأثير يمكن المبالغة فيه لأنه غالبًا ما يتم تعويضه بشكل كبير عن طريق تقليل عمليات البحث في الجداول غير المفهرسة بشكل كبير.
مؤشرات الأساليب
تدعم لغة C++ البرمجة كائنية التوجه ، لذا يمكن للفئات أن تحتوي على توابع (تُعرف عادةً بالدوال الأعضاء). تحتوي الدوال الأعضاء غير الثابتة (توابع النسخ) على مُعامل ضمني ( مؤشر this ) وهو مؤشر إلى الكائن الذي تعمل عليه، لذا يجب تضمين نوع الكائن كجزء من نوع مؤشر الدالة. ثم يُستخدم التابع على كائن من تلك الفئة باستخدام أحد مُعاملات "مؤشر إلى عضو": .*أو ->*(للكائن أو مؤشر إلى كائن، على التوالي).
على الرغم من أنه يمكن تنفيذ مؤشرات الدوال في لغتي C و C++ كعناوين بسيطة، إلا أنه عادةً ما sizeof(Fx) == sizeof(void*)يتم تنفيذ مؤشرات الأعضاء في C++ أحيانًا كمؤشرات "سميكة "، عادةً ما تكون ضعف أو ثلاثة أضعاف حجم مؤشر الدالة البسيط، وذلك للتعامل مع الأساليب الافتراضية والوراثة الافتراضية .
في لغة C++
في لغة C++، بالإضافة إلى الطريقة المستخدمة في لغة C، من الممكن أيضًا استخدام قالب فئة مكتبة C++ القياسية std::function ، والتي تكون مثيلاتها عبارة عن كائنات دالة:
استيراد std ؛دالة ثابتة للمشتقة المزدوجة ( دالة ثابتة من نوع std :: function < double ( double ) > & f , double x0 , double eps ) { double eps2 = eps / 2 ; double lo = x0 - eps2 ; double hi = x0 + eps2 ; return ( f ( hi ) - f ( lo )) / eps ; }static double f ( double x ) { return x * x ; }int main () { double x = 1 ; std :: println ( "d/dx(x ^ 2) [@ x = {}] = {}" , x , derivative ( f , x , 1e-5 )); return 0 ; }مؤشرات إلى الدوال الأعضاء في لغة C++
هكذا تستخدم لغة C++ مؤشرات الدوال عند التعامل مع الدوال الأعضاء في الأصناف أو الهياكل. يتم استدعاء هذه الدوال باستخدام مؤشر كائن أو استدعاء this. وهي آمنة من حيث النوع، إذ لا يمكنك استدعاء أعضاء ذلك الصنف (أو مشتقاته) إلا باستخدام مؤشر من ذلك النوع. يوضح هذا المثال أيضًا استخدام تعريف نوع (typedef) لمؤشر الدالة العضو، والذي أُضيف للتبسيط. أما مؤشرات الدوال إلى الدوال الأعضاء الثابتة فتُستخدم بأسلوب لغة C التقليدي، لأنه لا حاجة إلى مؤشر كائن لاستدعاء this.
استيراد std ؛class Foo { public : [[ nodiscard ]] static int add ( int i , int j ) noexcept { return i + j ; }[[ nodiscard ]] static int mult ( int i , int j ) noexcept { return i * j ; }[[ nodiscard ]] static int negate ( int i ) noexcept { return - i ; } };int bar1 ( int i , int j , Foo * pFoo , int ( Foo ::* pfn )( int , int )) { return ( pFoo ->* pfn )( i , j ); }typedef int ( Foo ::* Foo_pfn )( int , int );int bar2 ( int i , int j , Foo * pFoo , Foo_pfn pfn ) { return ( pFoo ->* pfn )( i , j ); }typedef auto ( * PFN )( int ) -> int ; // C++ فقط، نفس الشيء كما يلي: typedef int(*PFN)(int);int bar3 ( int i , PFN pfn ) { return pfn ( i ); }int main () { Foo foo ; std :: println ( "Foo::add(2,4) = {}" , bar1 ( 2 , 4 , & foo , & Foo :: add )); std :: println ( "Foo::mult(3,5) = {}" , bar2 ( 3 , 5 , & foo , & Foo :: mult )); std :: println ( "Foo::negate(6) = {}" , bar3 ( 6 , & Foo :: negate )); return 0 ; }صيغة بديلة للغة C و C++
إنّ صيغة C و C++ المذكورة أعلاه هي الصيغة القياسية المستخدمة في جميع الكتب الدراسية، ولكنها صعبة القراءة والشرح. حتى الأمثلة السابقة تستخدم هذه الصيغة. مع ذلك، يدعم كل مُصرّفtypedef C و C++ آليةً أكثر وضوحًا وإيجازًا لتعريف مؤشرات الدوال: استخدم `<function>` ، ولكن لا تخزّن المؤشر كجزء من التعريف. لاحظ أن الطريقة الوحيدة لاستخدام هذا النوع من `<function>` هي مع مؤشر، وهذا يُبرز طبيعته كمؤشر.typedeftypedef
لغة C ولغة C++
// هذا يُعلن عن الدالة 'F'، وهي دالة تقبل نوع البيانات 'char' وتُرجع نوع البيانات 'int'. التعريف موجود في مكان آخر. int F ( char c );// هذا يُعرّف 'Fn'، وهو نوع من الدوال التي تقبل 'char' وتُرجع 'int'. typedef int Fn ( char c );// يُعرّف هذا المتغير 'fn'، وهو من نوع مؤشر إلى 'Fn'، ويُسند إليه عنوان 'F'. Fn * fn = & F ; // لاحظ أن '&' ليس مطلوبًا، ولكنه يُوضّح ما يتم فعله.// يستدعي هذا الدالة 'F' باستخدام 'fn'، ويُسند النتيجة إلى المتغير 'a' int a = fn ( 'A' );// يُعرّف هذا الكود الدالة 'Call'، وهي دالة تقبل مؤشرًا إلى 'Fn'، وتستدعيها، وتعيد النتيجة. int Call ( Fn * fn , char c ) { return fn ( c ); } // Call(fn, c)// يستدعي هذا الدالة 'Call'، ويمرر إليها 'F'، ثم يُسند النتيجة إلى 'call' int call = Call ( & F , 'A' ); // مرة أخرى، '&' غير مطلوب// ملاحظة: للحفاظ على قواعد التعليمات البرمجية الحالية، لا يزال من الممكن استخدام نمط التعريف أعلاه أولاً؛ // ثم يمكن تعريف النوع الأصلي من حيثه باستخدام النمط الجديد.// هذا يُعرّف 'PFn'، وهو نوع من المؤشرات إلى النوع Fn. typedef Fn * PFn ;// يمكن استخدام 'PFn' أينما يمكن استخدام 'Fn *' PFn pfn = F ; int CallP ( PFn fn , char c );لغة سي++
Fnتستخدم هذه الأمثلة التعريفات المذكورة أعلاه. لاحظ على وجه الخصوص أنه يمكن استخدام التعريف المذكور أعلاه لـ في تعريفات المؤشرات إلى الدوال الأعضاء:
// يُعرّف هذا 'C'، وهي فئة تحتوي على دوال ثابتة ودوال أعضاء مماثلة، // ثم يُنشئ نسخة منها تُسمى 'c' class C { public : static int Static ( char c ); int Member ( char c ); } c ; // C// هذا يُعرّف 'p'، وهو مؤشر إلى 'C' ويُسند إليه عنوان 'c' C * p = & c ;// هذا يُسند مؤشرًا إلى 'Static' إلى 'fn'. // بما أنه لا يوجد 'this'، فإن 'Fn' هو النوع الصحيح؛ ويمكن استخدام 'fn' كما هو موضح أعلاه. fn = & C :: Static ;// يُعرّف هذا 'm'، وهو مؤشر إلى عضو في 'C' من النوع 'Fn'، // ويُسند إليه عنوان 'C::Member'. // يمكنك قراءته من اليمين إلى اليسار كجميع المؤشرات: // "'m' هو مؤشر إلى عضو في الفئة 'C' من النوع 'Fn'" Fn C ::* m = & C :: Member ;// يستخدم هذا 'm' لاستدعاء 'Member' في 'c'، وتعيين النتيجة إلى 'cA' int cA = ( c . * m )( 'A' );// يستخدم هذا 'm' لاستدعاء 'Member' في 'p'، وتعيين النتيجة إلى 'pA' int pA = ( p ->* m )( 'A' );// يُعرّف هذا 'Ref'، وهي دالة تقبل مرجعًا إلى 'C'، // ومؤشرًا إلى عضو من 'C' من النوع 'Fn'، و'char'، // وتستدعي الدالة وتعيد النتيجة int Ref ( C & r , Fn C ::* m , char c ) { return ( r . * m )( c ); } // Ref(r, m, c)// يُعرّف هذا 'Ptr'، وهي دالة تقبل مؤشرًا إلى 'C'، // ومؤشرًا إلى عنصر من 'C' من النوع 'Fn'، و'char'، // وتستدعي الدالة وتعيد النتيجة int Ptr ( C * p , Fn C ::* m , char c ) { return ( p ->* m )( c ); } // Ptr(p, m, c)// ملاحظة: للحفاظ على قواعد التعليمات البرمجية الحالية، لا يزال من الممكن استخدام نمط التعريف أعلاه أولاً؛ // ثم يمكن تعريف النوع الأصلي من حيثه باستخدام النمط الجديد.// هذا يُعرّف 'FnC'، وهو نوع من المؤشرات إلى عضو من الفئة 'C' من النوع 'Fn' typedef Fn C ::* FnC ;// يمكن استخدام 'FnC' أينما يمكن استخدام 'Fn C::*' FnC fnC = & C :: Member ; int RefP ( C & p , FnC m , char c );PL/I
يمكن تداخل إجراءات PL/I ، أي أن الإجراء A قد يحتوي على الإجراء B، الذي بدوره قد يحتوي على الإجراء C. بالإضافة إلى البيانات المُعلنة في B، يمكن لـ B أيضًا الإشارة إلى أي بيانات مُعلنة في A، طالما أنها لا تُبطل تعريفها. وبالمثل، يمكن لـ C الإشارة إلى البيانات في كل من A وB. لذلك، يجب أن تحتوي متغيرات ENTRY في PL/I على سياق [ 6 ] لتزويد الإجراء C بعناوين قيم البيانات في B وA وقت استدعاء C.
انظر أيضاً
مراجع
- ↑ أندرو ج. ميلر. "أمثلة فورتران" . تم الاسترجاع في 14-09-2013 .
- ↑ شعار "دروس مؤشرات الدوال" . مؤرشف من الأصل بتاريخ 16 مايو 2011. تم الاطلاع عليه بتاريخ 13 أبريل 2011.
مؤشرات الدوال هي مؤشرات، أي متغيرات، تشير إلى عنوان دالة
. - ↑ شعار "دروس مؤشرات الدوال" . مؤرشف من الأصل بتاريخ ١٦ مايو ٢٠١١. تم الاطلاع عليه بتاريخ ١٣ أبريل ٢٠١١.
ملاحظة هامة: يشير مؤشر الدالة دائمًا إلى دالة ذات توقيع محدد! لذا، يجب أن تحتوي جميع الدوال التي ترغب في استخدامها مع نفس مؤشر الدالة على نفس المعاملات ونوع القيمة المرجعة!
- ↑ "مؤشرات الدوال في لغة C# 9" . docs.microsoft.com . تم الاطلاع عليه بتاريخ 27 فبراير 2021 .
- ↑ "الخبرة: مستوى متوسط، اللغة: C++: استخدام الدوال الوظيفية (Functor) للوظائف المستدعاة في C++" . DevX.com. 31 يناير 2005. تاريخ الاطلاع: 13 أبريل 2011.
إذا كنت ترغب في استخدام دالة عضوية كدالة مستدعاة، فيجب ربط هذه الدالة بكائن من نفس الفئة قبل استدعائها. في هذه الحالة، يمكنك استخدام الدوال الوظيفية (Functor) [مع مثال في هذه الصفحة].
- ↑ أبراهامز، بول (مارس 1978). لغة البرمجة PL/I (ملف PDF) . مختبر كورانت للرياضيات والحوسبة، جامعة نيويورك. الصفحات 22-24 . تاريخ الاسترجاع: 4 أبريل 2025 .
روابط خارجية
- أسئلة شائعة حول مؤشرات الدوال ، وأمور يجب تجنبها عند استخدام مؤشرات الدوال، ومعلومات حول استخدام كائنات الدوال
- دروس حول مؤشرات الدوال، مؤرشفة بتاريخ 30-06-2018 في Wayback Machine ، دليل لمؤشرات الدوال في لغة C/C++، ووظائف الاستدعاء ، وكائنات الدوال (المؤثرات).
- مؤشرات الدوال الأعضاء وأسرع المندوبين الممكنين في لغة C++ ، مقال من CodeProject بقلم دون كلوغستون
- دروس تعليمية حول المؤشرات، مؤرشفة بتاريخ 5 أبريل 2009 على موقع Wayback Machine ، وثائق ودروس تعليمية حول لغة C++
- شرح المؤشرات في لغة C (مؤرشف بتاريخ 9 يونيو 2019 في أرشيف الإنترنت) دليل مرئي للمؤشرات في لغة C
- مؤشر الدالة الآمن ووظائف الاستدعاء في برمجة ويندوز ، مقال من CodeProject بقلم ر. سيلفام
- كتاب "C Book" ، مؤشرات الدوال في لغة C من تأليف "The C Book"
- مؤشرات الدوال في dBASE dBL ، مؤشر الدالة في dBASE dBL
- المؤشرات (برمجة الحاسوب)
- البرامج الفرعية
