C++: كائنات دوال STL

في الدرس 37 تعلّمنا عن المكرّرات.

الآن، سنتعلّم عن "روح" خوارزميات STL — كائنات الدوال.

الخوارزميات هي الهيكل، وكائنات الدوال هي اللحم. فقط عند دمجهما يتحرّر القوة الحقيقية لـ STL.


1. نظرة عامة على كائنات الدوال

(1) 1.1 ما هي كائنات الدوال؟

كائنات الدوال (Functors) هي كائنات يمكن استدعاؤها مثل الدوال.

ثلاثة أنواع من كائنات الدوال:

  1. مؤشرات الدوال
  2. فئات كائنات الدوال (تحميل operator() الزائد)
  3. تعبيرات Lambda (C++11)

(2) 1.2 لماذا نحتاج كائنات الدوال؟

خوارزميات STL عامة، لكن العمليات المحددة تختلف حسب الحاجة. تسمح كائنات الدوال بتخصيص العمليات.

تشبيه من الحياة الواقعية:



2. مؤشرات الدوال

(1) 2.1 الاستخدام الأساسي

مثال: فرز مخصص بمؤشرات الدوال (الصعوبة ⭐⭐)

▶ مثال 2: استخدام حاويات STL (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <vector>
#include <algorithm>

// دالة مقارنة مخصصة
bool compareDesc(int a, int b) {
 return a > b; // تنازلي
}

int main() {
 std::vector`<int>` v = {3, 1, 4, 1, 5, 9, 2, 6};
 
 // استخدام مؤشر الدالة
 std::sort(v.begin(), v.end(), compareDesc);
 
 for (int x : v) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

المخرجات:

TEXT 📖 للعرض فقط
3 1 4 1 5 9 2 6

النتيجة:

TEXT 📖 للعرض فقط
9 6 5 4 3 2 2 1 1 

💡 نصيحة:



3. فئات كائنات الدوال

(1) 3.1 ما هي فئات كائنات الدوال؟

فئات كائنات الدوال هي فئات تحمّل operator() بشكل زائد، ويمكن استدعاء نسخها مثل الدوال.

مثال: مقارن مخصص (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

// فئة كائن دالة: الفرز حسب طول السلسلة
struct CompareByLength {
 bool operator()(const std::string& a, const std::string& b) const {
 return a.length() < b.length();
 }
};

int main() {
 std::vector<std::string> words = {"apple", "banana", "cat", "dog"};
 
 // استخدام كائن الدالة
 std::sort(words.begin(), words.end(), CompareByLength());
 
 for (const auto& w : words) {
 std::cout << w << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

النتيجة:

TEXT 📖 للعرض فقط
cat dog apple banana 

(2) 3.2 مزايا كائنات الدوال

المقارنة مؤشر الدالة فئة كائن الدالة
الحالة بلا حالة يمكن أن تحمل حالة (متغيرات عضو)
الأداء قد لا تُدمَج ضمنيًا يمكن دمجها ضمنيًا، أسرع
المرونة منخفضة عالية (يمكن أن تكون قوالب)

(3) 3.3 كائنات دوال ذات حالة

مثال: كائن دالة عدّاد (الصعوبة ⭐⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <algorithm>
#include <vector>

// كائن دالة: عدّ العناصر التي تلبّي شرطًا
struct Counter {
 int threshold; // العتبة (حالة)
 
 Counter(int t) : threshold(t) {}
 
 bool operator()(int x) const {
 return x > threshold; // عدّ العناصر الأكبر من العتبة
 }
};

int main() {
 std::vector`<int>` v = {1, 5, 10, 15, 20};
 
 // إنشاء كائن دالة، تعيين العتبة إلى 10
 Counter counter(10);
 
 // عدّ العناصر الأكبر من 10
 int count = std::count_if(v.begin(), v.end(), counter);
 
 std::cout << "العناصر الأكبر من 10: " << count << std::endl; // المخرج: 2
 
 return 0;
}


4. تعبيرات Lambda

(1) 4.1 ما هي تعبيرات Lambda؟

تعبيرات Lambda هي دوال مجهولة أُدخلت في C++11 يمكن تعريفها مباشرة أينما تكون هناك حاجة لدالة.

الصياغة الأساسية:

CPP
[capture](parameters) -> return_type { body }
الجزء الوصف
capture قائمة الالتقاط (التقاط المتغيرات الخارجية)
parameters قائمة المعاملات
return_type نوع الإرجاع (يمكن حذفه)
body جسم الدالة

(2) 4.2 مثال أساسي

مثال: فرز بـ Lambda (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
 std::vector`<int>` v = {3, 1, 4, 1, 5, 9, 2, 6};
 
 // فرز بتعبير Lambda (تنازلي)
 std::sort(v.begin(), v.end(), [](int a, int b) {
 return a > b;
 });
 
 for (int x : v) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

المخرجات:

TEXT 📖 للعرض فقط
3 1 4 1 5 9 2 6

(3) 4.3 قائمة الالتقاط

تُحدّد قائمة الالتقاط أي المتغيرات الخارجية يمكن لـ Lambda الوصول إليها.

طريقة الالتقاط الوصف
[] عدم التقاط أي شيء
[x] التقاط x بالقيمة
[&x] التقاط x بالمرجع
[=] التقاط جميع المتغيرات بالقيمة
[&] التقاط جميع المتغيرات بالمرجع
[this] التقاط المؤشر this (يُستخدم داخل فئة)

مثال: Lambda مع حالة (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
 std::vector`<int>` v = {1, 5, 10, 15, 20};
 int threshold = 10;
 
 // التقاط threshold بالقيمة
 int count = std::count_if(v.begin(), v.end(),
 [threshold](int x) {
 return x > threshold;
 });
 
 std::cout << "العناصر الأكبر من " << threshold << ": " << count << std::endl;
 
 return 0;
}


5. كائنات الدوال المعرفة مسبقًا في STL

(1) 5.1 كائنات الدوال الحسابية

يوفر الرأس functional كائنات دوال شائعة:

كائن الدالة الغرض مثال
std::plus<T> الجمع std::plus<int>()
std::minus<T> الطرح std::minus<int>()
std::multiplies<T> الضرب std::multiplies<int>()
std::divides<T> القسمة std::divides<int>()
std::negate<T> النفي std::negate<int>()

مثال: مضاعفة باستخدام multiplies (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
 std::vector`<int>` v = {1, 2, 3, 4, 5};
 
 // مضاعفة جميع العناصر
 std::transform(v.begin(), v.end(), v.begin(),
 std::bind(std::multiplies`<int>`(), std::placeholders::_1, 2));
 
 for (int x : v) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

(2) 5.2 كائنات الدوال المقارنة

كائن الدالة الغرض
std::equal_to<T> يساوي
std::not_equal_to<T> لا يساوي
std::greater<T> أكبر من
std::less<T> أصغر من
std::greater_equal<T> أكبر من أو يساوي
std::less_equal<T> أصغر من أو يساوي

(3) 5.3 كائنات الدوال المنطقية

كائن الدالة الغرض
std::logical_and<T> و المنطقية
std::logical_or<T> أو المنطقية
std::logical_not<T> نفي المنطقية


6. مثال شامل

▶ مثال 1: معالج الدرجات (الصعوبة ⭐⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

struct Student {
 std::string name;
 int score;
};

int main() {
 std::vector`<Student>` students = {
 {"Zhang San", 85},
 {"Li Si", 92},
 {"Wang Wu", 78}
 };
 
 // 1. فرز تنازلي حسب الدرجة
 std::sort(students.begin(), students.end(),
 [](const Student& a, const Student& b) {
 return a.score > b.score;
 });
 
 // 2. إيجاد أعلى درجة
 auto max_it = std::max_element(students.begin(), students.end(),
 [](const Student& a, const Student& b) {
 return a.score < b.score;
 });
 
 std::cout << "أعلى درجة: " << max_it->name << " " << max_it->score << std::endl;
 
 // 3. عدّ الطلاب الناجحين
 int passed = std::count_if(students.begin(), students.end(),
 [](const Student& s) {
 return s.score >= 60;
 });
 
 std::cout << "الطلاب الناجحون: " << passed << std::endl;
 
 return 0;
}
▶ جرّب الكود

المخرجات:

TEXT 📖 للعرض فقط
أعلى درجة:  
الطلاب الناجحون:

❓ أسئلة شائعة

س: أيهما أفضل — Lambda أم فئات كائنات الدوال؟ ج:- العمليات البسيطة → Lambda (كود موجز) - العمليات المعقدة / تحتاج إعادة استخدام → فئات كائنات الدوال (قابلية صيانة أفضل)


س: هل يمكن لـ auto استنتاج نوع Lambda؟ ج: نوع Lambda هو نوع مجهول فريد؛ يمكن استنتاجه فقط بـ auto، ولا يمكن كتابته كنوع محدد.

TEXT 📖 للعرض فقط
auto func = [](int x) { return x * 2; };
// std::function<int(int)> func = ... // يعمل أيضًا، لكن بكلفة أداء

س: متى يجب أن أستخدم std::function؟ ج: استخدم std::function عندما تحتاج إلى تخزين كائنات دوال (كمتغيرات عضو، أو قيم إرجاع، إلخ).


▶ مثال 3: تعبير Lambda (الصعوبة ⭐)

CPP
#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector`<int>` v = {1, 2, 3, 4, 5};

    int sum = 0;
    std::for_each(v.begin(), v.end(), [&sum](int x) {
        sum += x;
    });

    std::cout << "المجموع: " << sum << std::endl;

    return 0;
}
▶ جرّب الكود

المخرجات:

TEXT 📖 للعرض فقط
1 2 3 4 5
💡 نصيحة: صياغة Lambda: [capture](parameters) { body }. [&sum] تلتقط بالمرجع، مما يسمح بتعديل المتغيرات الخارجية.


الموضوع النقاط الرئيسية
مؤشرات الدوال بسيطة لكن محدودة الوظائف
فئات كائنات الدوال عمليات قابلة للتخصيص، يمكنها حمل حالة
Lambda دوال مجهولة، موجزة وقوية
كائنات الدوال المعرفة مسبقًا std::plus وغيرها، في functional
قائمة الالتقاط كيف يصل Lambda إلى المتغيرات الخارجية

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): أنشئ كائن دالة (فئة تحمّل operator() بشكل زائد) "يقارن بين عددين صحيحين"، واختبره باستخدام std::sort.

  2. متوسط (الصعوبة ⭐⭐): استخدم std::function لتخزين أنواع مختلفة من الكائنات القابلة للاستدعاء (دوال عادية، وlambda، وكائنات دوال) واستدعائها بشكل موحّد.

  3. متقدم (الصعوبة ⭐⭐⭐): استخدم std::bind لربط جزء من المعاملات، مما ينشئ كائنات قابلة للاستدعاء جديدة. نفّذ محوّل دالة "تعبئة مسبقة للمعاملات".



الدرس التالي: محوّلات STL (#39)

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%