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 (الصعوبة ⭐)
#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;
}
الخرج:
First element: 1
Second element: 2
Size: 5
💡 نصيحة: vec.at(i) تُجري فحص حدود وتُلقي استثناءً عند تجاوزها؛ vec[i] لا تتحقق، وهي أكثر كفاءة.
(2) 2.2 إضافة وحذف العناصر
#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: المرور بالفهرس (الأكثر شيوعاً)
#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، موصى بها)
#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: المُكرِّرات (ستُغطّى لاحقاً)
#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 (الصعوبة ⭐)
#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;
}
الخرج:
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 (الصعوبة ⭐⭐)
#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;
}
الخرج:
1 5 10 20
5. list (قائمة مترابطة مزدوجة)
(1) 5.1 ما هي list؟
std::list هي قائمة مترابطة مزدوجة - كل عنصر يخزن عنواني العنصر السابق والتالي.
| المقارنة | vector | list |
|---|---|---|
| وصول عشوائي | O(1) | ❌ غير مدعوم |
| إدراج/حذف في الوسط | O(n) | O(1) |
| استخدام الذاكرة | صغير | كبير (مؤشران إضافيان لكل عنصر) |
▶ مثال 4: الاستخدام الأساسي لـ list (الصعوبة ⭐⭐)
#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;
}
الخرج:
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 (الصعوبة ⭐⭐)
#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؟ ج: افهم المفاهيم الأساسية أولاً، ثم عززها من خلال الأمثلة العملية.
📖 ملخص
- حاويات STL هي هياكل بيانات جاهزة توفرها مكتبة C++ القياسية
- vector هو الأكثر استخداماً (مصفوفة ديناميكية)
- array للسيناريوهات ثابتة الحجم
- deque يدعم العمليات السريعة في كلا الطرفين
- list قائمة مترابطة مزدوجة، جيدة للإدراج/الحذف المتكرر
- يُفضّل vector ما لم يكن لديك سبب واضح لاستخدام شيء آخر
📝 تمارين
-
أساسي (الصعوبة ⭐): استخدم
std::vector<int>لتخزين 5 أعداد صحيحة، مرر عليها وأخرجها. -
متوسط (الصعوبة ⭐⭐): استخدم
std::vector<std::string>لتخزين 3 نصوص، دع المستخدم يُدخلها، ثم أخرجها. -
متقدم (الصعوبة ⭐⭐⭐): استخدم
std::dequeلتطبيق "كشف المتطابقات": -
قارن من كلا الطرفين نحو المنتصف؛ إذا تطابقت جميع المحارف المقابلة، فهي متطابقة
-
مثال:
"racecar"متطابقة،"hello"ليست كذلك
- فئات حاويات STL: متسلسلة (vector/list/deque) وتجميعية (set/map)
- vector مصفوفة ديناميكية: إضافة/حذف سريع في النهاية، يدعم الوصول العشوائي
- list قائمة مترابطة مزدوجة: إدراج سريع في أي موضع
- map حاوية مفتاح-قيمة: مرتبة بالمفتاح، بحث O(log n)
- set حاوية مجموعة: عناصر فريدة، مرتبة تلقائياً
9. 🚀 الخطوات التالية
الآن بعد أن تعلمت أساسيات حاويات STL، سندرس بعد ذلك خوارزميات STL (الدرس 35) - استخدام الخوارزميات التي توفرها المكتبة القياسية لمعالجة الحاويات، حتى لا تضطر لكتابة الفرز والبحث بنفسك...