C++: أساسيات حاويات STL

في الدروس السابقة، تعلمنا القوالب - جعل الدوال والفئات تدعم أي نوع.

لكن في المشاريع الواقعية، لا تحتاج لكتابة حاوياتك الخاصة (مثل المصفوفات الديناميكية أو القوائم المترابطة).

مكتبة القوالب القياسية في C++ (STL) توفر بالفعل حاويات جاهزة - استخدمها مباشرة!


1. ما هي STL؟

STL (مكتبة القوالب القياسية) هي جزء من مكتبة C++ القياسية، تحتوي على:

المكون الغرض أمثلة
الحاويات تخزين البيانات std::vector، std::array
المُكرِّرات المرور على الحاويات begin()، end()
الخوارزميات معالجة البيانات std::sort، std::find
كائنات الدوال منطق مقارنة مخصص std::less، std::greater

💡 نقطة مهمة: STL مُنفَّذة بـ القوالب، لذا فهي تدعم أي نوع.



2. vector (الأكثر استخداماً)

(1) 2.1 ما هو vector؟

std::vector هو مصفوفة ديناميكية - حجمها ينمو تلقائياً.

المقارنة مصفوفة عادية vector
الحجم ثابت ديناميكي
الذاكرة مكدس أو كومة كومة
الوصول للعناصر arr[i] vec[i] أو vec.at(i)
التوصية ⭐⭐ ⭐⭐⭐⭐⭐

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

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

int main() {
 // إنشاء vector يخزن أعداد int
 std::vector<int> vec = {1, 2, 3, 4, 5};
 
 // الوصول للعناصر
 std::cout << "First element: " << vec[0] << std::endl;
 std::cout << "Second element: " << vec.at(1) << std::endl;
 
 // تعديل العناصر
 vec[0] = 100;
 
 // الحصول على الحجم
 std::cout << "Size: " << vec.size() << std::endl;
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
First element: 1
Second element: 2
Size: 5

💡 نصيحة: vec.at(i) تُجري فحص حدود وتُلقي استثناءً عند تجاوزها؛ vec[i] لا تتحقق، وهي أكثر كفاءة.


(2) 2.2 إضافة وحذف العناصر

CPP
#include <iostream>
#include <vector>

int main() {
 std::vector<int> vec;
 
 // إضافة عناصر إلى النهاية
 vec.push_back(10);
 vec.push_back(20);
 vec.push_back(30);
 
 // حذف العنصر الأخير
 vec.pop_back();
 
 // إدراج عنصر في موضع محدد
 vec.insert(vec.begin() + 1, 15); // إدراج 15 في الموضع الثاني
 
 // حذف عنصر في موضع محدد
 vec.erase(vec.begin() + 1); // حذف العنصر الثاني
 
 return 0;
}

(3) 2.3 المرور على vector

الطريقة 1: المرور بالفهرس (الأكثر شيوعاً)

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

int main() {
 std::vector<int> vec = {1, 2, 3, 4, 5};
 
 for (size_t i = 0; i < vec.size(); i++) {
 std::cout << vec[i] << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

الطريقة 2: for المبنية على النطاق (C++11، موصى بها)

CPP
#include <iostream>
#include <vector>

int main() {
 std::vector<int> vec = {1, 2, 3, 4, 5};
 
 for (int x : vec) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

الطريقة 3: المُكرِّرات (ستُغطّى لاحقاً)

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

int main() {
 std::vector<int> vec = {1, 2, 3, 4, 5};
 
 for (auto it = vec.begin(); it != vec.end(); ++it) {
 std::cout << *it << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

💡 التوصية: يُفضّل for المبنية على النطاق (C++11) - هي الأكثر إيجازاً.



3. array (مصفوفة ثابتة الحجم)

(1) 3.1 ما هو array؟

std::array هو مصفوفة ثابتة الحجم، لكنها أكثر أماناً من المصفوفة العادية.

المقارنة مصفوفة عادية array
الحجم يجب معرفته احصل عليه بـ size()
هل يتحلل إلى مؤشر؟ نعم لا
التوصية ⭐⭐ ⭐⭐⭐⭐

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

CPP
#include <iostream>
#include <array>

int main() {
 // إنشاء array يخزن 5 أعداد int
 std::array<int, 5> arr = {1, 2, 3, 4, 5};
 
 // الوصول للعناصر
 std::cout << "First element: " << arr[0] << std::endl;
 
 // الحصول على الحجم
 std::cout << "Size: " << arr.size() << std::endl;
 
 return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
First element: 1
Size: 5

💡 نقطة مهمة: حجم std::array يُحدد في وقت الترجمة ولا يمكن تغييره.



4. deque (طابور مزدوج النهاية)

(1) 4.1 ما هو deque؟

std::deque هو طابور مزدوج النهاية - يمكنك إضافة/حذف عناصر بسرعة في كلا الطرفين.

العملية vector deque
إضافة في النهاية O(1) O(1)
إضافة في البداية O(n) O(1)
وصول عشوائي O(1) O(1)

▶ مثال 3: الاستخدام الأساسي لـ deque (الصعوبة ⭐⭐)

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

int main() {
 std::deque<int> dq;
 
 // إضافة في النهاية
 dq.push_back(10);
 dq.push_back(20);
 
 // إضافة في البداية
 dq.push_front(5);
 dq.push_front(1);
 
 // dq الآن: 1, 5, 10, 20
 
 for (int x : dq) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
1 5 10 20


5. list (قائمة مترابطة مزدوجة)

(1) 5.1 ما هي list؟

std::list هي قائمة مترابطة مزدوجة - كل عنصر يخزن عنواني العنصر السابق والتالي.

المقارنة vector list
وصول عشوائي O(1) ❌ غير مدعوم
إدراج/حذف في الوسط O(n) O(1)
استخدام الذاكرة صغير كبير (مؤشران إضافيان لكل عنصر)

▶ مثال 4: الاستخدام الأساسي لـ list (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <list>

int main() {
 std::list<int> lst = {1, 2, 3, 4, 5};
 
 // إضافة في البداية
 lst.push_front(0);
 
 // إضافة في النهاية
 lst.push_back(6);
 
 // حذف العناصر بقيمة 3
 lst.remove(3);
 
 for (int x : lst) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}
▶ جرّب الكود

الخرج:

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


6. forward_list (قائمة مترابطة أحادية)

(1) 6.1 ما هي forward_list؟

std::forward_list هي قائمة مترابطة أحادية - كل عنصر يخزن عنوان العنصر التالي فقط.

المقارنة list forward_list
استخدام الذاكرة أكبر أصغر
هل يمكن المرور للخلف؟
التوصية ⭐⭐⭐⭐ ⭐⭐ (لسيناريوهات محددة)

💡 نصيحة: ما لم تكن متأكداً أنك تحتاج المرور للأمام فقط، استخدم std::list.



7. دليل اختيار الحاوية

السيناريو الحاوية الموصى بها
تحتاج حجماً ديناميكياً std::vector
حجم ثابت، تريد توافقاً مع C std::array
تحتاج إضافة/حذف سريع في البداية std::deque
إدراج/حذف متكرر في الوسط std::list
تحتاج المرور للأمام فقط وتوفير الذاكرة std::forward_list

💡 القاعدة الذهبية: يُفضّل std::vector ما لم يكن لديك سبب واضح لاستخدام شيء آخر.



8. تطبيق: مصفوفة ديناميكية باستخدام vector (الصعوبة ⭐⭐)

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

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

int main() {
 std::vector<Student> students;
 
 // إضافة طلاب
 students.push_back({"Alice", 20, 92.5});
 students.push_back({"Bob", 21, 88.0});
 students.push_back({"Charlie", 19, 95.0});
 
 // المرور والإخراج
 for (const auto& s : students) {
 std::cout << "Name: " << s.name 
 << ", Age: " << s.age 
 << ", Score: " << s.score << std::endl;
 }
 
 return 0;
}

❓ أسئلة شائعة

س: ما الفرق بين vector و array؟ ج:> - vector حجمه ديناميكي، مُخزَّن على الكومة > - array حجمه ثابت، مُخزَّن على المكدس > > نصيحة الاختيار: إذا كان الحجم غير مؤكد، استخدم vector؛ إذا كان الحجم ثابتاً وصغيراً، استخدم array. س: لماذا يُنصح بـ for المبنية على النطاق للمرور على vector؟ ج: لأنها أكثر إيجازاً وأقل عرضة لأخطاء شروط الحلقة. > > // for تقليدية (سهل الخطأ في الشروط) > for (size_t i = 0; i < vec.size(); i++) { ... } > > // for مبنية على النطاق (موجزة، أقل عرضة للخطأ) > for (int x : vec) { ... } > س: أيهما أسرع - list أم vector؟ ج: يعتمد على السيناريو: > - إذا احتجت وصولاً عشوائياً (vec[100]vector أسرع > - إذا احتجت إدراج/حذف متكرر في الوسط، list أسرع


س: ما أهم شيء في حاويات STL؟ ج: افهم المفاهيم الأساسية أولاً، ثم عززها من خلال الأمثلة العملية.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): استخدم std::vector<int> لتخزين 5 أعداد صحيحة، مرر عليها وأخرجها.

  2. متوسط (الصعوبة ⭐⭐): استخدم std::vector<std::string> لتخزين 3 نصوص، دع المستخدم يُدخلها، ثم أخرجها.

  3. متقدم (الصعوبة ⭐⭐⭐): استخدم std::deque لتطبيق "كشف المتطابقات":

  4. قارن من كلا الطرفين نحو المنتصف؛ إذا تطابقت جميع المحارف المقابلة، فهي متطابقة

  5. مثال: "racecar" متطابقة، "hello" ليست كذلك


9. 🚀 الخطوات التالية

الآن بعد أن تعلمت أساسيات حاويات STL، سندرس بعد ذلك خوارزميات STL (الدرس 35) - استخدام الخوارزميات التي توفرها المكتبة القياسية لمعالجة الحاويات، حتى لا تضطر لكتابة الفرز والبحث بنفسك...

Web-Tutorial.com

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

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

100%