C++: مكرّرات STL

في الدرسين 34-35 تعلّمنا عن حاويات وخوارزميات STL.

الآن، سنتعمّق في "الصمغ" الذي يربط STL — المكرّرات.

فهم المكرّرات هو المفتاح لفهم فلسفة تصميم STL حقًا.


1. نظرة عامة على المكرّرات

(1) 1.1 ما هي المكرّرات؟

المكرّرات هي مفهوم أساسي في STL تربط الحاويات بالخوارزميات.

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


(2) 1.2 العمليات الأساسية للمكرّرات

تدعم جميع المكرّرات العمليات التالية:

العملية الوصف مثال
*it فك المرجع int x = *it;
it++ الانتقال للعنصر التالي ++it;
it-- الرجوع للعنصر السابق --it;
it1 == it2 مقارنة المساواة if (it1 == it2)
it1 != it2 مقارنة عدم المساواة while (it != end())


2. فئات المكرّرات

(1) 2.1 أنواع المكرّرات الخمسة

تُعرّف STL خمسة أنواع من المكرّرات، من الأضعف إلى الأقوى:

نوع المكرّر القدرات الحاوية الممثّلة
مكرّر الإدخال قراءة فقط، أحادي الاتجاه istream_iterator
مكرّر الإخراج كتابة فقط، أحادي الاتجاه ostream_iterator
مكرّر أمامي قراءة/كتابة، أحادي الاتجاه forward_list
مكرّر ثنائي الاتجاه قراءة/كتابة، ثنائي الاتجاه list, set, map
مكرّر الوصول العشوائي قراءة/كتابة، وصول عشوائي vector, deque, array

(2) 2.2 مقارنة قدرات المكرّرات

TEXT 📖 للعرض فقط
مكرّر الإدخال ← الأضعف
 ↓
مكرّر أمامي
 ↓
مكرّر ثنائي الاتجاه
 ↓
مكرّر الوصول العشوائي ← الأقوى

كلما زادت القدرة، زادت العمليات المدعومة:

العملية الإدخال أمامي ثنائي الاتجاه وصول عشوائي
فك المرجع *
التقديم ++
التأخير --
وصول عشوائي []
حساب + -

(3) 2.3 مثال: مكرّرات لحاويات مختلفة

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

int main() {
 std::vector`<int>` v = {1, 2, 3};
 std::list`<int>` l = {1, 2, 3};
 std::forward_list`<int>` fl = {1, 2, 3};
 
 // vector: مكرّر الوصول العشوائي
 auto it_v = v.begin();
 std::cout << it_v[2] << std::endl; // الوصول العشوائي يعمل
 
 // list: مكرّر ثنائي الاتجاه
 auto it_l = l.begin();
 ++it_l; // يمكن التقديم
 --it_l; // يمكن التأخير
 // it_l[2]; // ❌ خطأ! list لا يدعم الوصول العشوائي
 
 return 0;
}

المخرجات:

TEXT 📖 للعرض فقط
3


3. بطلان المكرّرات

(1) 3.1 ما هو بطلان المكرّرات؟

يحدث بطلان المكرّر عندما تُصبح عملية على الحاوية موضع المكرّر غير صالح.

الأسباب الشائعة:


(2) 3.2 قواعد بطلان المكرّرات حسب الحاوية

الحاوية العملية البطلان
vector push_back قد يبطل (عندما يحدث إعادة تخصيص)
vector erase تُبطَل المكرّرات عند العنصر المحذوف وبعده
list push_back لا يبطل
list erase يُبطَل فقط مكرّر العنصر المحذوف
map/set erase يُبطَل فقط مكرّر العنصر المحذوف

(3) 3.3 مثال: بطلان مكرّر vector (الصعوبة ⭐⭐)

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

CPP
#include <iostream>
#include <vector>

int main() {
 std::vector`<int>` v = {1, 2, 3, 4, 5};
 
 auto it = v.begin();
 std::cout << "*it = " << *it << std::endl; // المخرج: 1
 
 // قد يسبب push_back إعادة تخصيص، مما يبطل المكرّرات
 v.push_back(6);
 
 // ❌ خطير! قد يكون it مبطَلاً
 // std::cout << "*it = " << *it << std::endl; // سلوك غير معرّف
 
 // ✅ الطريقة الصحيحة: إعادة الحصول على المكرّر
 it = v.begin();
 std::cout << "*it = " << *it << std::endl; // المخرج: 1
 
 return 0;
}
▶ جرّب الكود

المخرجات:

TEXT 📖 للعرض فقط
*it = 1
*it = 1

(4) 3.4 إزالة العناصر بأمان

الطريقة الخاطئة:

TEXT 📖 للعرض فقط
for (auto it = v.begin(); it != v.end(); ++it) {
### ▶ مثال 2: تطبيق ميزات C++ الحديثة (الصعوبة ⭐)

 if (*it % 2 == 0) {
 v.erase(it); // ❌ it مبطَل!
 }
}

الطريقة الصحيحة:

CPP
for (auto it = v.begin(); it != v.end(); ) {
 if (*it % 2 == 0) {
 it = v.erase(it); // ✅ erase يُرجع المكرّر الصالح التالي
 } else {
 ++it;
 }
}

المخرجات:

TEXT 📖 للعرض فقط
(مخرجات البرنامج)


4. المكرّرات العكسية

(1) 4.1 ما هي المكرّرات العكسية؟

المكرّرات العكسية تجتاز الحاوية من النهاية إلى البداية.

مثال: الإخراج العكسي (الصعوبة ⭐)

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

int main() {
 std::vector`<int>` v = {1, 2, 3, 4, 5};
 
 // استخدام مكرّر عكسي
 for (auto it = v.rbegin(); it != v.rend(); ++it) {
 std::cout << *it << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

النتيجة:

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


5. مكرّرات الإدراج

(1) 5.1 ما هي مكرّرات الإدراج؟

مكرّرات الإدراج هي مكرّرات إخراج تُستخدم لإدراج عناصر في حاوية.

ثلاثة أنواع من مكرّرات الإدراج:

المكرّر الوظيفة مثال
back_inserter إدراج في الخلف std::back_inserter(v)
front_inserter إدراج في الأمام std::front_inserter(l)
inserter إدراج في موضع محدد std::inserter(v, v.begin())

(2) 5.2 مثال: النسخ باستخدام back_inserter (الصعوبة ⭐⭐)

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

int main() {
 std::vector`<int>` src = {1, 2, 3, 4, 5};
 std::vector`<int>` dst; // حاوية فارغة
 
 // ❌ خطأ! dst لا يملك مساحة كافية
 // std::copy(src.begin(), src.end(), dst.begin());
 
 // ✅ صحيح! استخدم back_inserter للتوسيع التلقائي
 std::copy(src.begin(), src.end(), std::back_inserter(dst));
 
 std::cout << "نتيجة النسخ: ";
 for (int x : dst) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

المخرجات:

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


6. مكرّرات التدفق

(1) 6.1 مكرّرات تدفق الإدخال

الوظيفة: قراءة البيانات من تدفق إدخال.

مثال: القراءة من المدخلات القياسية (الصعوبة ⭐⭐)

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

int main() {
 std::vector`<int>` v;
 
 std::cout << "أدخل بعض الأرقام (Ctrl+Z للإنهاء):" << std::endl;
 
 // القراءة من المدخلات القياسية
 std::copy(std::istream_iterator`<int>`(std::cin),
 std::istream_iterator`<int>`(),
 std::back_inserter(v));
 
 std::cout << "أدخلت: ";
 for (int x : v) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

(2) 6.2 مكرّرات تدفق الإخراج

الوظيفة: كتابة البيانات إلى تدفق إخراج.

مثال: الإخراج إلى ملف (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>

int main() {
 std::vector`<int>` v = {1, 2, 3, 4, 5};
 
 // الإخراج إلى المخرجات القياسية
 std::copy(v.begin(), v.end(),
 std::ostream_iterator`<int>`(std::cout, " "));
 std::cout << std::endl;
 
 // الإخراج إلى ملف
 std::ofstream file("output.txt");
 std::copy(v.begin(), v.end(),
 std::ostream_iterator`<int>`(file, "\n"));
 file.close();
 
 return 0;
}

❓ أسئلة شائعة

س لماذا لا يدعم list الوصول العشوائي؟
ج list هو قائمة مرتبطة — العناصر غير متجاورة في الذاكرة، لذا لا يمكن الوصول إليها مباشرة بالفهرس.

س كيف أتجنب بطلان المكرّر؟
ج 1. أعد الحصول على المكرّرات بعد كل عملية على الحاوية 2. استخدم القيمة المُرجعة من erase لتحديث المكرّرات 3. فضّل استخدام الخوارزميات على معالجة المكرّرات يدويًا

س: ما هو const_iterator؟ ج: const_iterator هو مكرّر للقراءة فقط لا يمكنه تعديل قيم العناصر.

TEXT 📖 للعرض فقط
std::vector`<int>` v = {1, 2, 3};
std::vector`<int>`::const_iterator it = v.cbegin();
// *it = 10; // ❌ خطأ! لا يمكن التعديل

▶ مثال 3: الاجتياز بالمكرّرات (الصعوبة ⭐)

CPP
#include <iostream>
#include <vector>

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

    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

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

المخرجات:

TEXT 📖 للعرض فقط
1 2 3 4 5
💡 نصيحة: تعمل المكرّرات مثل المؤشرات — استخدم * لفك المرجع، و++ للتحرك، و!= لمقارنة المواضع.


الموضوع النقاط الرئيسية
فئات المكرّرات الإدخال → أمامي → ثنائي الاتجاه → وصول عشوائي
بطلان المكرّرات vector قد يُبطِل، list لا يُبطِل
المكرّرات العكسية rbegin()/rend()
مكرّرات الإدراج back_inserter وغيرها
مكرّرات التدفق تربط STL بعمليات الإدخال/الإخراج

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): استخدم المكرّرات لاجتياز vector<int> وإخراج جميع العناصر. افعل ذلك باستخدام begin/end وحلقة for النطاقية.

  2. متوسط (الصعوبة ⭐⭐): استخدم المكرّرات العكسية rbegin/rend لاجتياز vector بشكل عكسي وإخراجه. لاحظ الفرق عن الاجتياز الأمامي.

  3. متقدم (الصعوبة ⭐⭐⭐): نفّذ مكرّرًا مخصصًا يغلّف نطاقًا صحيحًا (مثلاً من 1 إلى 10)، يدعم عاملَي ++ و*.



الدرس التالي: كائنات دوال STL (#38)

Web-Tutorial.com

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

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

100%