C++: كائنات دوال STL
في الدرس 37 تعلّمنا عن المكرّرات.
الآن، سنتعلّم عن "روح" خوارزميات STL — كائنات الدوال.
الخوارزميات هي الهيكل، وكائنات الدوال هي اللحم. فقط عند دمجهما يتحرّر القوة الحقيقية لـ STL.
1. نظرة عامة على كائنات الدوال
(1) 1.1 ما هي كائنات الدوال؟
كائنات الدوال (Functors) هي كائنات يمكن استدعاؤها مثل الدوال.
ثلاثة أنواع من كائنات الدوال:
- مؤشرات الدوال
- فئات كائنات الدوال (تحميل
operator()الزائد) - تعبيرات Lambda (C++11)
(2) 1.2 لماذا نحتاج كائنات الدوال؟
خوارزميات STL عامة، لكن العمليات المحددة تختلف حسب الحاجة. تسمح كائنات الدوال بتخصيص العمليات.
تشبيه من الحياة الواقعية:
- الخوارزمية = غسالة (عامة)
- كائن الدالة = مسحوق غسيل (قابل للتخصيص: رائحة منعشة / قوي / منعم للأقمشة)
2. مؤشرات الدوال
(1) 2.1 الاستخدام الأساسي
مثال: فرز مخصص بمؤشرات الدوال (الصعوبة ⭐⭐)
▶ مثال 2: استخدام حاويات STL (الصعوبة ⭐)
#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;
}
المخرجات:
3 1 4 1 5 9 2 6
النتيجة:
9 6 5 4 3 2 2 1 1
💡 نصيحة:
- مؤشرات الدوال إرث من لغة C؛ يُوصى في C++ باستخدام كائنات الدوال أو Lambda بدلاً منها
3. فئات كائنات الدوال
(1) 3.1 ما هي فئات كائنات الدوال؟
فئات كائنات الدوال هي فئات تحمّل operator() بشكل زائد، ويمكن استدعاء نسخها مثل الدوال.
مثال: مقارن مخصص (الصعوبة ⭐⭐)
#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;
}
النتيجة:
cat dog apple banana
(2) 3.2 مزايا كائنات الدوال
| المقارنة | مؤشر الدالة | فئة كائن الدالة |
|---|---|---|
| الحالة | بلا حالة | يمكن أن تحمل حالة (متغيرات عضو) |
| الأداء | قد لا تُدمَج ضمنيًا | يمكن دمجها ضمنيًا، أسرع |
| المرونة | منخفضة | عالية (يمكن أن تكون قوالب) |
(3) 3.3 كائنات دوال ذات حالة
مثال: كائن دالة عدّاد (الصعوبة ⭐⭐⭐)
#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 يمكن تعريفها مباشرة أينما تكون هناك حاجة لدالة.
الصياغة الأساسية:
[capture](parameters) -> return_type { body }
| الجزء | الوصف |
|---|---|
capture |
قائمة الالتقاط (التقاط المتغيرات الخارجية) |
parameters |
قائمة المعاملات |
return_type |
نوع الإرجاع (يمكن حذفه) |
body |
جسم الدالة |
(2) 4.2 مثال أساسي
مثال: فرز بـ Lambda (الصعوبة ⭐)
#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;
}
المخرجات:
3 1 4 1 5 9 2 6
(3) 4.3 قائمة الالتقاط
تُحدّد قائمة الالتقاط أي المتغيرات الخارجية يمكن لـ Lambda الوصول إليها.
| طريقة الالتقاط | الوصف |
|---|---|
[] |
عدم التقاط أي شيء |
[x] |
التقاط x بالقيمة |
[&x] |
التقاط x بالمرجع |
[=] |
التقاط جميع المتغيرات بالقيمة |
[&] |
التقاط جميع المتغيرات بالمرجع |
[this] |
التقاط المؤشر this (يُستخدم داخل فئة) |
مثال: Lambda مع حالة (الصعوبة ⭐⭐)
#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 (الصعوبة ⭐)
#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: معالج الدرجات (الصعوبة ⭐⭐⭐)
#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;
}
المخرجات:
أعلى درجة:
الطلاب الناجحون:
❓ أسئلة شائعة
س: أيهما أفضل — Lambda أم فئات كائنات الدوال؟ ج:- العمليات البسيطة → Lambda (كود موجز) - العمليات المعقدة / تحتاج إعادة استخدام → فئات كائنات الدوال (قابلية صيانة أفضل)
س: هل يمكن لـ
autoاستنتاج نوع Lambda؟ ج: نوع Lambda هو نوع مجهول فريد؛ يمكن استنتاجه فقط بـauto، ولا يمكن كتابته كنوع محدد.
auto func = [](int x) { return x * 2; };
// std::function<int(int)> func = ... // يعمل أيضًا، لكن بكلفة أداء
س: متى يجب أن أستخدم
std::function؟ ج: استخدمstd::functionعندما تحتاج إلى تخزين كائنات دوال (كمتغيرات عضو، أو قيم إرجاع، إلخ).
▶ مثال 3: تعبير Lambda (الصعوبة ⭐)
#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;
}
المخرجات:
1 2 3 4 5
[capture](parameters) { body }. [&sum] تلتقط بالمرجع، مما يسمح بتعديل المتغيرات الخارجية.
| الموضوع | النقاط الرئيسية |
|---|---|
| مؤشرات الدوال | بسيطة لكن محدودة الوظائف |
| فئات كائنات الدوال | عمليات قابلة للتخصيص، يمكنها حمل حالة |
| Lambda | دوال مجهولة، موجزة وقوية |
| كائنات الدوال المعرفة مسبقًا | std::plus وغيرها، في functional |
| قائمة الالتقاط | كيف يصل Lambda إلى المتغيرات الخارجية |
📖 ملخص
- كائنات الدوال: فئات تحمّل
operator()بشكل زائد، قابلة للاستدعاء مثل الدوال - Lambda: دوال مجهولة، الصياغة
[capture](parameters) { body } - std::function: غلاف دالة عام، يمكنه تخزين أي كائن قابل للاستدعاء
- std::bind: يربط معاملات الدوال، مما ينشئ كائنات قابلة للاستدعاء جديدة
📝 تمارين
-
أساسي (الصعوبة ⭐): أنشئ كائن دالة (فئة تحمّل operator() بشكل زائد) "يقارن بين عددين صحيحين"، واختبره باستخدام std::sort.
-
متوسط (الصعوبة ⭐⭐): استخدم std::function لتخزين أنواع مختلفة من الكائنات القابلة للاستدعاء (دوال عادية، وlambda، وكائنات دوال) واستدعائها بشكل موحّد.
-
متقدم (الصعوبة ⭐⭐⭐): استخدم std::bind لربط جزء من المعاملات، مما ينشئ كائنات قابلة للاستدعاء جديدة. نفّذ محوّل دالة "تعبئة مسبقة للمعاملات".
- كائنات الدوال (functors): فئات تحمّل operator() بشكل زائد
- كائنات الدوال يمكنها حمل حالة؛ الدوال العادية لا تستطيع
- std::function: غلاف قابل للاستدعاء مع محو النوع
- bind يربط جزء من المعاملات لينشئ كائنات قابلة للاستدعاء جديدة
- Lambda هو سكر نحوي لكائنات الدوال
الدرس التالي: محوّلات STL (#39)