سؤال

هل هناك طريقة "مناسبة" لتنفيذ وظائف ترتيب أعلى في C.

أشعر بالفضول في الغالب بشأن أشياء مثل قابلية النقل وصحة بناء الجملة هنا وإذا كان هناك أكثر من طرق ما هي المزايا والعيوب.

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

لذلك هذه هي خطتي في الأساس:

typedef gpointer (converter_func_type)(PyObject *)

gpointer converter_function(PyObject *obj)
{
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function);
}

وللتوضيح السؤال: أريد أن أعرف كيفية القيام بذلك بأكثر أمانًا وأكثر صحة C. أود حقًا الحفاظ على نمط وظيفة الطلب العالي ولكن إذا كان ذلك مستهجنًا ، فأنا أقدر تقديراً إلى حد كبير للقيام بذلك بطريقة أخرى.

هل كانت مفيدة؟

المحلول

إذا كنت حريصًا على القيام بذلك في Plain C ، فيجب عليك أن تتذكر أن تتضمن خيار تمرير مؤشر سياق من المتصل من Functor (وظيفة الترتيب العالي) إلى الوظيفة التي تم تمريرها. وهذا يتيح لك محاكاة ما يكفي من الإغلاق الذي يمكنك جعل الأشياء تعمل بسهولة كافية. ما يشير إليه هذا المؤشر ... حسنًا ، الأمر متروك لك ، لكن يجب أن يكون void* في واجهة برمجة تطبيقات Functor (أو أحد الأسماء المستعارة العديدة لذلك ، مثل gpointer في عالم اللمعان أو ClientData في TCL C API).

تحرير]: لاستخدام/تكييف مثالك:

typedef gpointer (converter_func_type)(gpointer,PyObject *)

gpointer converter_function(gpointer context_ptr,PyObject *obj)
{
    int *number_of_calls_ptr = context_ptr;
    *number_of_calls_ptr++;
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f, gpointer context_ptr)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(context_ptr,item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   int number_of_calls = 0;
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function, &number_of_calls);
   // Now number_of_calls has how often converter_function was called...
}

هذا مثال تافه على كيفية القيام بذلك ، ولكن يجب أن يوضح لك الطريق.

نصائح أخرى

من الناحية الفنية ، فإن الوظائف ذات الترتيب العالي هي مجرد وظائف تأخذ أو إرجاع وظائف. لذا فإن أشياء مثل QSort هي بالفعل ترتيب أعلى.

إذا كنت تقصد شيئًا أشبه بوظائف Lambda الموجودة في اللغات الوظيفية (وهو المكان الذي تصبح فيه وظائف الترتيب العليا مفيدة حقًا) ، فهذه أصعب قليلاً ولا يمكن القيام بها بشكل طبيعي في المعيار الحالي C. إنها ليست مجرد جزء من اللغة. تمديد كتل Apple هو أفضل مرشح. إنه يعمل فقط في GCC (ومترجم C LLVM) ، لكنها مفيدة حقًا. نأمل أن يكون هناك شيء من هذا القبيل. إليك بعض الموارد ذات الصلة:

تكمن المشكلة الكبيرة في تنفيذ الوظائف ذات الترتيب العالي في C إلى أن تفعل أي شيء غير تافهة تحتاج إلى إغلاق ، والتي هي مؤشرات وظيفية معززة بهياكل البيانات التي تحتوي على متغيرات محلية يمكنهم الوصول إليها. نظرًا لأن الفكرة الكاملة وراء عمليات الإغلاق هي التقاط المتغيرات المحلية وتمريرها إلى جانب مؤشر الوظيفة ، فمن الصعب القيام به دون دعم البرمجيات. وحتى مع دعم برنامج التحويل البرمجي ، من الصعب القيام به بدون جمع القمامة لأن المتغيرات يمكن أن توجد خارج نطاقها ، مما يجعل من الصعب معرفة وقت تحريرها.

في المستقيم C ، يتم ذلك حقًا فقط من خلال مؤشرات الوظائف ، والتي هي ألم ولا تهدف إلى هذا النوع من الأشياء (وهذا جزئيًا سبب ألم). الكتل (أو الإغلاق ، وفقا لغير apple) رائعة لهذا ، رغم ذلك. يتم تجميعها في GCC -4.x أو شيء من هذا القبيل ، و ICC شيء ، ولكن بغض النظر عن هذا ما تبحث عنه. لسوء الحظ ، لا يمكنني العثور على أي برامج تعليمية جيدة عبر الإنترنت ، لكن يكفي أن أقول إنها تعمل شيئًا من هذا القبيل:

void iterate(char *str, int count, (^block)(str *)){
  for(int i = 0; i < count; i++){
    block(list[i]);
  }
}

main() {
  char str[20];
  iterate(str, 20, ^(char c){
    printf("%c ", c);
  });

  int accum = 0;
  iterate(someList, 20, ^(char c){
    accum += c;
    iterate(str, 20, ^(char c){
      printf("%c ", c);
    });
  });
}

من الواضح أن هذا الرمز لا معنى له ، لكنه يطبع كل حرف من خيوط (STR) مع مساحة بينه ، ثم يضيف جميع الأحرف معًا إلى مخصص ، وفي كل مرة تقوم فيها بطباعة قائمة الأحرف مرة أخرى.

أتمنى أن يساعدك هذا. بالمناسبة ، تكون الكتل مرئية للغاية في Mac OS X Snow Leopard API-S ، وأعتقد أنها في معيار C ++ 0x القادم ، لذلك فهي ليست غير عادية حقًا.

من الناحية العملية ، يتطلب أي تطبيق وظيفة أعلى من الترتيب المثير للاهتمام إغلاقًا ، والذي يستلزم في C روتين متطورة ومعرض للخطأ لتحديد وسيطات وظائف الهيكل يدويًا.

هذا إجابة على السؤال: كيفية تكوين وظائف في C ، والتي يتم إعادة توجيهها هنا.

يمكنك إنشاء بنية بيانات لتنفيذ نوع بيانات القائمة. يمكن أن يحتوي هذا الهيكل على مؤشرات الوظيفة.

#include<stdlib.h>
#include<malloc.h>

typedef (*fun)();

typedef struct funList { fun car; struct funList *cdr;} *funList;

const funList nil = NULL;

int null(funList fs){ return nil==fs; }

fun car(funList fs)
{
   if(!null(fs)) return fs->car; 
   else 
   {
     fprintf(stderr,"error:can't car(nil) line:%d\n",__LINE__);
     exit(1);
   }
}

funList cdr(funList ls)
{ if(!null(ls)) return ls->cdr; 
  else 
  {
    fprintf(stderr,"error:can't cdr(nil) line:%d\n",__LINE__);
    exit(1);
  }
}

funList cons(fun f, funList fs)
{  funList ls;

   ls=(funList) malloc(sizeof(struct funList));
   if(NULL==ls)
   {
     fprintf(stderr,"error:can't alloc mem for cons(...) line:%d\n",__LINE__);
     exit(1);
   }

   ls->car=f;
   ls->cdr=fs;

   return ls;
}

يمكننا كتابة شركات دالة تنطبق على قائمة الوظائف:

type_2 comp(funList fs, type_1 x)
{  
   return (null(fs)) ? x : car(fs)(comp(cdr(fs),x)); 
}

مثال على كيفية عمله. نستخدم (FGH) كترميز قصير للسلبيات (F ، Cons (G ، Cons (H ، NIL))) ، والذي يتم تطبيقه على وسيطة معينة X:

comp((f g h),x)

=

f(comp((g h),x))

=

f(g(comp((h),x)))

=

f(g(h(comp(nil,x))))

=

f(g(h(x)))

إذا كنت قد استخدمت نوع القائمة المتعددة الأشكال بلغة مطبوعة مثل SML أو Haskell ، فيجب أن يكون نوع شركات:

comp :: ([a -> a],a) -> a

لأنه في هذا السياق جميع الأعضاء في القائمة لديهم نفس النوع. C يمكن أن يكون أكثر مرونة بهذا المعنى. ربما شيء مثل

typedef void (*fun)();

أو

typedef (*fun)();

يجب أن ترى ما يقوله دليل C حول هذا. وتأكد من أن جميع الوظائف المتجاورة لها أنواع متوافقة.

يجب أن تكون الوظائف التي يجب تأليفها نقية ، أي بدون آثار جانبية ولا متغيرات حرة.

من الصعب جدًا القيام به في مستقيم C. إنه أكثر ممكنًا في C ++ (انظر تعليمي functors أو تعزيز ربط و وظيفة المكتبات). أخيراً، C ++ 0x يضيف الدعم الأصلي لوظائف Lambda, ، والتي تهتم بك بالتقاط جميع المتغيرات التي يعتمد عليها funcion.

إذا كنت ترغب في إنشاء وظائف ذات ترتيب أعلى ، فلا تستخدم C. هناك حلول C لمشكلتك. قد لا تكون أنيقة ، أو قد تكون أكثر أناقة التي تدركها.

تحرير] اقترحت أن الطريقة الوحيدة لتحقيق ذلك هي استخدام لغة البرمجة النصية. لقد اتصل بي الآخرون. لذا ، أقوم باستبدال هذا الاقتراح بهذا: [/edit

ما الذي تحاول تحقيقه؟ إذا كنت ترغب في تقليد عمليات الإغلاق ، فاستخدم لغة تدعمها (يمكنك ربطها بـ Ruby و Lua و JavaScript ، إلخ من خلال المكتبات). إذا كنت ترغب في استخدام عمليات الاسترجاعات ، فإن مؤشرات الوظائف على ما يرام. تجمع مؤشرات الوظيفة بين أكثر المناطق خطورة في C (المؤشرات ونظام النوع الضعيف) ، لذا كن حذرًا. إعلانات مؤشر الوظيفة ليست ممتعة للقراءة أيضًا.

تجد بعض مكتبات C باستخدام مؤشرات الوظائف لأنها يجب أن. إذا كنت تكتب مكتبة ، فربما تحتاج إلى استخدامها أيضًا. إذا كنت تستخدمها فقط داخل التعليمات البرمجية الخاصة بك ، فربما لا تفكر في C. أنت تفكر في Lisp أو Scheme أو Ruby أو ... وتحاول كتابته بطريقة C.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top