المتجه (Vector) 1
Sequence containers في C++ هي فئة من الـcontainers الموجودة في مكتبة النماذج القياسية (STL) التي تخزن العناصر بترتيب تسلسلي. إنها توفر تخصيصًا ديناميكيًا للتخزين وتدعم العمليات المختلفة للوصول إلى العناصر وإدراجها وإزالتها. يتم تصنيف Sequence containers بشكل أساسي بناءً على كيفية تنظيم عناصرها وإدارتها. فيما يلي شرح لـSequence containers شائعة الاستخدام:
المصفوفات Arrays
تعتبر المصفوفات بمثابة نوع من sequence container في لغة C++. ومع ذلك، يتم تصنيفها عادةً بشكل منفصل عن sequence containers الأخرى مثل الـvectors والـlists وdeques بسبب طبيعة حجمها الثابت وغياب إمكانيات تغيير الحجم الديناميكي.
المتجه Vector
الخصائص:
- إدراج سريع/إزالة سريعة في النهاية
- إدراج بطيء/إزالة بطيئة في البداية أو في المنتصف
- بحث بطيء
مثال 1:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v(4); v[0] = 10; v.push_back(50); return 0; }
يحاول مقتطف التعليمات البرمجية هذا إنشاء متجه للأعداد الصحيحة بحجم أولي مكون من 4 عناصر، ويقوم بتعيين قيمة للعنصر الأول، ثم يحاول إضافة عنصر إضافي باستخدام دالة Push_back. ومع ذلك، هناك مشكلة محتملة في استخدام Push_back بعد تحديد الحجم الأولي.
دعونا نحلل الكود ونناقش الآثار المترتبة:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(4); // Creates a vector with 4 elements, all initialized to 0 v[0] = 10; // Assigns a value of 10 to the first element of the vector v.push_back(50); // Attempts to add an additional element with the value 50 // This line may lead to undefined behavior because it's // appending an element beyond the originally specified size return 0; }
شرح:
- السطر vector v(4); ينشئ متجهًا v بحجم أولي مكون من 4 عناصر، تمت تهيئتها جميعًا إلى القيمة الافتراضية للأعداد الصحيحة (وهي 0).
- ثم، v[0] = 10؛ يعين قيمة 10 للعنصر الأول من المتجه.
- ومع ذلك، السطر التالي v.push_back(50); يحاول إضافة عنصر إضافي إلى المتجه باستخدام Push_back. نظرًا لأن حجم المتجه كان في البداية يحتوي على 4 عناصر فقط، فإن إلحاق عنصر يتجاوز هذا الحجم قد يؤدي إلى سلوك غير محدد. قد تؤدي عملية Push_back إلى إعادة تخصيص وحدة التخزين الأساسية للمتجه، مما قد يؤدي إلى تلف الذاكرة أو أي سلوك آخر غير متوقع.
مثال 2:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {1, 2, 3, 4}; // Initialize vector v with values 1, 2, 3, 4 vector<int>v2(v); // Initialize vector v2 as a copy of v cout << v2[0]; // Output the first element of v2 return 0; }
يقوم مقتطف الكود هذا بتهيئة المتجه v بأربعة أعداد صحيحة {1، 2، 3، 4}، ثم يحاول تهيئة المتجه v2 الآخر كنسخة من v.
سيكون الناتج 1، لأنه يطبع العنصر الأول من v2، وهو نسخة من v. لذلك، سيطبع العنصر الأول من v، وهو 1.
مثال 3:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(5, 100); // Initialize vector v with 5 elements, each set to the value 100 vector<int> v2(6, 200); // Initialize vector v2 with 6 elements, each set to the value 200 cout << v2[0] << endl; // Output the first element of v2 return 0; }
في هذا البرنامج:
- vector v(5, 100); يقوم بإنشاء متجه v مع 5 عناصر، كل منها تمت تهيئته بالقيمة 100. يقوم هذا المُنشئ بتهيئة v بخمسة عناصر، كل منها له القيمة 100.
- vector v2(6, 200); ينشئ متجه v2 مع 6 عناصر، كل منها تمت تهيئته بالقيمة 200. يقوم هذا المنشئ بتهيئة v2 بـ 6 عناصر، كل منها له القيمة 200.
- cout << v2[0] << endl; يطبع العنصر الأول من v2، وهو 200، متبوعًا بحرف السطر الجديد.
وبالتالي فإن مخرجات هذا الكود ستكون:
200
مثال 4:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(5, 100); // Initialize vector v with 5 elements, each set to the value 100 vector<int> v2(6, 200); // Initialize vector v2 with 6 elements, each set to the value 200 v.swap(v2); // Swap the contents of v and v2 cout << v2[0] << endl; // Output the first element of v2 return 0; }
في هذا البرنامج:
- vector v(5, 100); ينشئ متجهًا v مكونًا من 5 عناصر، تمت تهيئ كل منها إلى القيمة 100.
- vector v2(6, 200); ينشئ متجهًا v2 مكونًا من 6 عناصر، تمت تهيئ كل منها إلى القيمة 200.
- v.swap(v2); يتبادل محتويات v وv2، ويتبادل بشكل فعال عناصر وأحجام المتجهين.
- cout << v2[0] << endl; يحاول طباعة العنصر الأول من v2.
ومع ذلك، بعد عملية المبادلة، يحتوي v2 الآن على العناصر الموجودة في الأصل في v، ويحتوي v على العناصر الموجودة في الأصل في v2. لذلك، فإن محاولة الوصول إلى v2[0] ستؤدي فعليًا إلى طباعة العنصر الأول من المتجه الذي كان في البداية v.
وبالتالي فإن مخرجات هذا الكود ستكون:
100
مثال 5:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v; // Create an empty vector v.push_back(10); // Add 10 to the end of the vector v.push_back(20); // Add 20 to the end of the vector v.push_back(30); // Add 30 to the end of the vector cout << v.front() << endl; // Output the first element of the vector cout << v.back() << endl; // Output the last element of the vector cout << v.at(0) << endl; // Output the element at index 0 of the vector return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v;: يعلن عن متجه فارغ للأعداد الصحيحة.
- v.push_back(10);: إلحاق القيمة 10 بنهاية المتجه v.
- v.push_back(20);: إلحاق القيمة 20 بنهاية المتجه v.
- v.push_back(30);: إلحاق القيمة 30 بنهاية المتجه v.
- cout << v.front() << endl;: يُخرج العنصر الأول من المتجه v باستخدام دالة العضو front(). في هذه الحالة، فإنه يطبع 10.
- cout << v.back() << endl;: يُخرج العنصر الأخير من المتجه v باستخدام دالة العضو back(). في هذه الحالة، فإنه يطبع 30.
- cout << v.at(0) << endl;: إخراج العنصر عند الـ index 0 للمتجه v باستخدام دالة العضو at(). في هذه الحالة، فإنه يطبع 10.
وبالتالي فإن مخرجات هذا الكود ستكون:
10 30 10
مثال 6:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(100); // Create a vector with 100 elements, all initialized to 0 v.push_back(5); // Append the value 5 to the end of the vector cout << "Size = " << v.size() << endl; // Output the current size of the vector cout << "Capacity = " << v.capacity() << endl; // Output the current capacity of the vector return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v(100);: إنشاء متجه v بحجم أولي مكون من 100 عنصر، تمت تهيئتها جميعًا إلى القيمة الافتراضية لـ int، وهي 0.
- v.push_back(5);: إلحاق القيمة 5 بنهاية المتجه. نظرًا لأنه تمت تهيئة المتجه بحجم 100، ولكن تمت إضافة عنصر واحد فقط، فسيقوم المتجه بتغيير حجم نفسه حسب الحاجة لاستيعاب العنصر الجديد.
- cout << “Size = ” << v.size() << endl;: إخراج الحجم الحالي للمتجه باستخدام دالة العضو size(). في هذه الحالة، يتم طباعة 101، حيث يوجد الآن 101 عنصر في المتجه.
- cout << “Capacity = ” << v.capacity() << endl;: إخراج السعة الحالية للمتجه باستخدام دالة عضو السعة (). تمثل السعة الحد الأقصى لعدد العناصر التي يمكن أن يحتفظ بها المتجه دون إعادة تخصيص الذاكرة. في هذه الحالة، يعتمد ذلك على التنفيذ، ولكن من المحتمل أن يكون أكبر من أو يساوي 101، حيث قد يخصص المتجه ذاكرة إضافية لتجنب عمليات إعادة التخصيص المتكررة عند إضافة المزيد من العناصر.
مثال 7:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {1, 2, 3, 4, 5}; for (size_t i = 0; i < v.size(); i++) { cout << v[i] << " "; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {1, 2, 3, 4, 5};: تهيئة المتجه v بخمسة عناصر، يحتوي كل منها على القيم 1، 2، 3، 4، و5، على التوالي.
- for (size_t i = 0; i < v.size(); i++) { … }: هذه حلقة for تقليدية تتكرر فوق كل عنصر من عناصر المتجه. يبدأ بفهرس i يساوي 0 ويستمر حتى يصبح i أقل من حجم المتجه (v.size()).
- size_t هو نوع عدد صحيح غير موقّع يستخدم لتمثيل أحجام الكائنات. يُستخدم بشكل شائع للمؤشرات وأحجام الحاويات مثل المتجهات.
- cout << v[i] << ” “;: داخل الحلقة، يتم طباعة قيمة العنصر عند index i للمتجه، متبوعًا بمسافة.
وبالتالي فإن مخرجات هذا الكود ستكون:
1 2 3 4 5
يقوم بطباعة كل عنصر من عناصر المتجه v مفصولاً بمسافة.
Deque
الخصائص:
- إدراج سريع/إزالة سريعة في البداية والنهاية
- إدراج سريع/إزالة بطيئة في المنتصف
- بحث بطيء
List
الخصائص:
- سرعة إدخال وإزالة العناصر من أي مكان في الـcontainer
- الوصول العشوائي السريع غير مدعوم. إنه بطيء في الوصول إلى العناصر من المنتصف
- بحث بطيء
- قائمة مرتبطة بشكل مزدوج
Forward List
الخصائص:
- إدراج وإزالة سريعين للعناصر من أي مكان في الـcontainer.
- الوصول العشوائي السريع غير مدعوم
- قائمة مرتبطة بشكل منفرد