• 0
Mr.B

تعابير lambda في C++11

سؤال

السلام عليكم،

سنتحدث في هذا المقال عن الامبدا، وهي إضافة لمعايير c++11، تأكد من أن لديك مصرف c++ حديث، إذا كنت تستخدم gcc قد تحتاج لتمرير الخيار -std=c++11:
 

g++ -Wall -std=c++11 file.cpp

أضافت معايير c++11 دوال الـ"لامبدا lambda"، وهي دوال بدون اسم ومستخدمهة في كثير من اللغات.

متى تستخدمها؟ أحياناً تمر عليك حالة تضطر فيها لإنشاء دالة لن تستخدمها إلا مرة واحدة كما في هذا المثال:
 

#include <iostream>#include <vector>#include <algorithm>static inline bool count_negatives(int n) {  return n < 0;}int main(int argc, char **argv) {  std::vector<int> v = {1, -2, 3, 4, -5, 6, 7};  std::cout << "negative numbers : "            << std::count_if(v.begin(), v.end(), count_negatives)            << std::endl;  return 0;}

يطبع:
 

negative numbers : 2

نريد حساب عدد الأعداد السالبة في v عن طريق استخدام std::count_if، ومررنا الدالة count_negatives، إذا كان الرقم سالب فستعيد true.

الملاحظ أن الدالة count_negatives على الأرجح ستستخدم مرة وترميها (هذا يتكرر خاصة مع قوالب algorithm)، الإختصار في مثل هذه الحالة أن تستخدم اللامبدا كالتالي:
 

#include <iostream>#include <vector>#include <algorithm>int main(int argc, char **argv) {  std::vector<int> v = {1, -2, 3, 4, -5, 6, 7};  std::cout << "negative numbers : "            << std::count_if(v.begin(), v.end(), [](int n) { return n < 0; })            << std::endl;  return 0;}

هذه هي الامبدا:
 

[](int n) {  return n < 0;}

مثل الدوال العادية إلا أنها بدون إسم، قد يبدو شكلها غريب إلا أنك متى مابدأت تجربتها ستجدها بسيطة.

يمكنك إنشاء الامبدا واستدعاءها في نفس اللحظة كما في المثال:
 

#include <iostream>int main(int argc, char **argv) {  []() { std::cout << "Hello" << std::endl; }();  return 0;}

ويمكنك أن تمرر لها المتغيرات وتسند القيم التي تعيدها لمتغيرات هكذا:
 

#include <iostream>int main(int argc, char **argv) {  float n = -10.0f;  std::cout << n << std::endl;  n = [](float f) { return f < 0 ? -f : f; }(n);  std::cout << n << std::endl;  return 0;}

صيغة الامبدا (الصورة من هنا):


IC251606.png

* 1 : إفتراضياً الامبدا لايمكن لها أن تقرأ الدوال خارج جسمها، إنظر هنا:
 

#include <iostream>int main(int argc, char **argv) {  int n = 1;  []() { std::cout << n << std::endl; }();  return 0;}

سيعطيك المصرف خطأ لأنك تحاول قراءة n، يمكنك أن تمرر n كمتغير:
 

#include <iostream>int main(int argc, char **argv) {  int n = 1;  [](int n) { std::cout << n << std::endl; }(n);  return 0;}

أو تمررها عن طريق الأقواس []:
 

#include <iostream>int main(int argc, char **argv) {  int n = 1;  [n]() { std::cout << n << std::endl; }();  return 0;}

هذا ستمرر نسخة من n، يمكنك أن تمرر مرجع لها إذأ أردت أن تغير قيمتها:
 

#include <iostream>int main(int argc, char **argv) {  int n = 1;  [&n]() { n = 2; }();  std::cout << n << std::endl;  return 0;}

يمكنك كتابة أكثر من متغير [a, &b, c, &d ..].

يمكنك أيضاً الإختصار وتمرر كل المتغيرات كمرجع بمكتابة [&] أو تمرير نسخ من جميع المتغيرات باستخدام [=]:
 

[&]() { /* .. */ }; // كل المتغيرات ستمرر كمرجع[=]() { /* .. */ }; // ستمرر نسخة من كل المتغيرات

* 2 : المتغيرات التي تمررها للامبدا، مثل الدوال العادية، تمرير بالقيمة بالمرجع بالمؤشر.
* 3 : لما تمرر متغير عن طريق القيمة فلا يسمح لك بتعديله كما لو كان const، رغم أنه نسخه لن تؤثر على المتغير الأصلي:
 

int main(int argc, char **argv) {  int n = 1;  [=]() { n = 2; }(); // خطأ، لايسمح لك بتعديل n  return 0;}

سيعطي المصرف خطأ. يمكنك استخدام mutable هنا لتجاوز هذه المشكلة وسيسمح لك بتعديل قيمته:
 

int main(int argc, char **argv) {  int n = 1;  [=]() mutable { n = 2; }(); // لا يوجد خطأ  return 0;}

كما قلت لن تؤثر على المتغير الأصلي:
 

#include <iostream>int main(int argc, char **argv) {  int n = 1;  [=]() mutable {    std::cout << n << std::endl;    n = 2;    std::cout << n << std::endl;  }();  std::cout << n << std::endl; // لن يتغير  return 0;}

سيطبع:
 

121

* 4: تخص الإستثناءات لن أتطرق لها.
* 5: لاحظ في الأمثلة السابقة أننا لم نحدد نوع القيمة التي ستعيدها الامدا، إفتراضياً لو كتبت:
 

#include <iostream>int main(int argc, char **argv) {  float n = -10.0f;  std::cout << n << std::endl;  n = [](float f) { return f < 0 ? -f : f; }(n);  std::cout << n << std::endl;  return 0;}

فسيعرف المصرف أنك تريد إعادة float، لكن ماذا لو أردت تحديد القيمة التي تعيدها، خذ مثلاً هذا المثال:
 

#include <iostream>int main(int argc, char **argv) {  std::cout << []() { return 'A' + 1; }() << std::endl;  return 0;}

سيطبع لك 66، السبب أن المصرف ضمنياً حول 'A' + 1 إلى int، وأنت تريد char، الحل أن تحدد نوع القيمة المعادة هكذا:
 

[]() -> char { return 'A' + 1; }();

سهم ثم نوع القيمة المعادة، الآن سيطبع 'B':
 

#include <iostream>int main(int argc, char **argv) {  std::cout << []() -> char { return 'A' + 1; }() << std::endl;  return 0;}

* 6: تمثل جسم الدالة، وأعتقد أنها بديهية.

بعض الأشياء عند التعامل مع الامدا، لو أردت استخدام الامبدا أكثر من مرة، يمكنك انشاء متغير auto يتولى المصرف بنفسه عملية تحويله لنوع الامبدا:
 

#include <iostream>int main(int argc, char **argv) {  auto lambda = []() { std::cout << "Hello world!" << std::endl; };  lambda();  lambda();  lambda();  return 0;}

أو يمكنك استخدام القالب std::function إذا أردت تحديد نوع الامبدا بنفسك:
 

#include <iostream>#include <functional>int main(int argc, char **argv) {  std::function<void ()> lambda = []() { std::cout << "Hello world!" << std::endl; };  lambda();  lambda();  lambda();  return 0;}

مثال آخر:
 

#include <iostream>#include <functional>int main(int argc, char **argv) {  std::function<float (int)> lambda = [](int n) -> float { return n + 0.5f; };  std::cout << lambda(1) << std::endl;  std::cout << lambda(2) << std::endl;  std::cout << lambda(3) << std::endl;  return 0;}

بالتوفيق

تم تعديل بواسطه Mr.B
6

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

2 إجابة على هذا السؤال .

  • 0

ما شاء الله درس جميل وممتاز

 

بالفعل نحتاج إلى دروس جديدة لتطوير أنفسنا, لان فى الفترة الاخيرة منتدى C++ أصبح أكثر مواضيعة الغاز واسئلة وبس, لكننها أيضا نحتاح ركن خاص بالمواضيع والمقالات الجديدة حتى تكتمل الحياة السى بلس بلسوية :D ..

ننتظر منك المزيد أخى من الواضيع الشيقة ولا يشترط أن تتكلم عن شئ أو خاصية جديد ولكن ممكن أيضا أن تتكلم عن مواضيع وخصائص قديمة ولكن غير مكررة .

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

درس رائع بارك الله فيك ,

كان لدي سؤال : هل يمكن كتابة دالة عودية باستخدام lambda expressions ? ( فعلياً لا أحتاج تابع بشكل مستعجل داخل الmain إلا إذا كان recursive لأن الباقي يمكن كتابته بدون التوابع)

كانت الإجابة هنا بنعم

وهذا مثال

std::function<int (int)> factorial = [&] (int i)        {            return (i == 1) ? 1 : i * factorial(i - 1);        };    std::cout<<factorial(5);

إضافة رائعة في c++11  , شكراً لك

والله ولي التوفيق

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



سجل دخولك الان

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .