المتجه (Vector) 2
التكرارات Iterators
في لغة C++، التكرارات عبارة عن كائنات توفر طريقة لاجتياز عناصر الحاوية (مثل المتجهات) بطريقة تسلسلية. تعمل التكرارات كمؤشرات للعناصر الموجودة في الحاوية، مما يسمح لك بالوصول إلى العناصر ومعالجتها بكفاءة. إنها بمثابة جسر بين الخوارزميات وهياكل البيانات، مما يتيح لك تنفيذ عمليات متنوعة على عناصر الحاوية دون الحاجة إلى معرفة تفاصيل التنفيذ الأساسية.
بالنسبة للمتجهات على وجه التحديد، توفر التكرارات العديد من الوظائف:
- الوصول إلى العناصر: يمكنك استخدام التكرارات للوصول إلى العناصر الفردية للمتجه. تدعم المُكرِّرات إلغاء المرجعية dereferencing، مما يسمح لك باسترداد قيمة العنصر الذي تشير إليه.
- الاجتياز: توفر التكرارات آليات للعبور عبر عناصر المتجه بشكل تسلسلي. يمكنك زيادة أو إنقاص المكرر للانتقال إلى العنصر التالي أو السابق، على التوالي.
- العمليات المستندة إلى النطاق: يمكن للتكرارات تحديد النطاقات داخل المتجه، مما يسمح لك بتنفيذ عمليات على مجموعات فرعية من العناصر. على سبيل المثال، يمكنك تحديد نطاق من التكرارات للإشارة إلى مجموعة فرعية من العناصر لفرزها أو نسخها إلى حاوية أخرى.
- فئات التكرار: يتم تصنيف التكرارات في لغة C++ إلى فئات مختلفة بناءً على قدراتهم وسلوكهم. بالنسبة للمتجهات، فئة التكرار الأكثر استخدامًا هي مكرر الوصول العشوائي، الذي يدعم الوصول العشوائي الفعال إلى العناصر باستخدام العمليات الحسابية مثل الجمع والطرح.
- إبطال المكرر: يمكن أن يؤدي تعديل المتجه (على سبيل المثال، إدراج عناصر أو مسحها) إلى إبطال المكررات التي تشير إلى عناصر داخل المتجه. من الضروري أن تكون على دراية بقواعد إبطال المكرر لتجنب السلوك غير المحدد عند العمل مع المكررات.
مثال 1:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; vector<int>::iterator it = v.begin(); cout << *it; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {10, 20, 30, 40};: تهيئة المتجه v بأربعة أعداد صحيحة {10, 20, 30, 40}.
- Vector::iterator it = v.begin();: يعلن عن مكرر من النوع Vector::iterator ويقوم بتهيئته للإشارة إلى بداية المتجه v باستخدام دالة العضو begin().
- cout << *it;: يقوم بإلغاء الإشارة إلى المكرر للوصول إلى القيمة التي يشير إليها (العنصر الأول في المتجه) وطباعتها باستخدام cout.
يطبع قيمة العنصر الأول من المتجه v، وهو 10.
مثال 2:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; vector<int>::iterator it = v.end()-1; cout << *it; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector::iterator it = v.end() – 1;: يعلن عن مكرر من النوع Vector::iterator ويقوم بتهيئته للإشارة إلى العنصر الأخير في المتجه v عن طريق طرح 1 من النتيجة من دالة العضو end(). لاحظ أن end() تُرجع مكررًا يشير إلى الموضع بعد العنصر الأخير، لذا فإن طرح 1 يوجهه إلى العنصر الأخير.
- cout << *it;: يقوم بإلغاء الإشارة إلى المكرر للوصول إلى القيمة التي يشير إليها (العنصر الأخير في المتجه) وطباعته باستخدام cout.
يطبع قيمة العنصر الأخير من المتجه v، وهو 40.
مثال 3:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::iterator it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر لكل عنصر من عناصر المتجه باستخدام مكرر it. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يساوي المكرر v.end()، والذي يمثل موضعًا واحدًا بعد نهاية المتجه. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
10 20 30 40
مثال 4:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::reverse_iterator it = v.rbegin(); it != v.rend(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة بترتيب عكسي باستخدام التكرارات العكسية. يبدأ بالمكرر العكسي الذي يشير إلى العنصر الأخير في المتجه (v.rbegin()) ويستمر حتى يصل المكرر العكسي إلى الموضع قبل العنصر الأول (v.rend())، حصريًا. في كل تكرار، تتم زيادة المكرر العكسي للإشارة إلى العنصر السابق (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر العكسي للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
40 30 20 10
مثال 5:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::const_reverse_iterator it = v.crbegin(); it != v.crend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::const_reverse_iterator it = v.crbegin(); it != v.crend(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة بترتيب عكسي باستخدام مكررات const العكسية . يبدأ بالمكرر العكسي const الذي يشير إلى العنصر الأخير في المتجه (v.crbegin()) ويستمر حتى يصل المكرر العكسي const إلى الموضع قبل العنصر الأول (v.crend()). في كل تكرار، تتم زيادة المكرر العكسي const للإشارة إلى العنصر السابق (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء الإشارة إلى المكرر العكسي const للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
يقوم بطباعة كل عنصر من عناصر المتجه v بترتيب عكسي، بدءًا من 40 وانتهاءً بالرقم 10.
مثال 6:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::const_iterator it = v.cbegin(); it != v.cend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::const_iterator it = v.cbegin(); it != v.cend(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام مكررات const. يبدأ بمكرر const الذي يشير إلى العنصر الأول في المتجه (v.cbegin()) ويستمر حتى يصل مكرر const إلى الموضع بعد العنصر الأخير (v.cend()). في كل تكرار، تتم زيادة مكرر const للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية مكرر const للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
يقوم بطباعة كل عنصر من عناصر المتجه v على سطر جديد، ويتكرر خلال المتجه بأكمله.
Auto
في لغة C++، يتم استخدام الكلمة المفتاحية auto لاستدلال النوع، مما يسمح للمترجم باستنتاج نوع المتغير تلقائيًا بناءً على مُهيئه. عند استخدامه مع المتجهات، يمكن لـ auto تبسيط التعليمات البرمجية عن طريق تحديد النوع الصحيح من التكرارات أو العناصر تلقائيًا دون تحديدها بشكل صريح.
يمكن أن يؤدي استخدام auto مع المتجهات إلى جعل التعليمات البرمجية أكثر إيجازًا وقابلية للقراءة، خاصة عندما يكون المكرر أو نوع العنصر معقدًا أو عندما يتم تغييره بشكل متكرر. ومع ذلك، من الضروري استخدام auto بحكمة والتأكد من أن النوع المستنتج واضح ولا لبس فيه.
مثال 7:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {5, 10, 15, 20}; // Initialize vector v with four integers auto it = v.begin(); // Use auto to deduce the type of the iterator // Iterate over the vector elements using the deduced iterator type for (; it != v.end(); it++) { cout << *it << endl; // Output the value pointed to by the iterator, followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {5, 10, 15, 20};: تهيئة المتجه v بأربعة أعداد صحيحة {5, 10, 15, 20}.
- auto it = v.begin();: يستخدم auto لاستنتاج نوع المكرر. في هذه الحالة، يستنتج أنه من النوع Vector::iterator، يشير إلى بداية المتجه v.
- for (; it != v.end(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة باستخدام نوع المكرر المستنتج. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5 10 15 20
مثال 8:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.insert(v.begin()+1, 12); v.insert(v.end()-1, 17); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.insert(v.begin() + 1, 12);: يُدرج القيمة 12 في المتجه v في الموضع بعد العنصر الأول (index 1).
- v.insert(v.end() – 1, 17);: يُدرج القيمة 17 في المتجه v في الموضع قبل العنصر الأخير. نظرًا لأن v.end() يشير إلى العنصر الأخير، فإن طرح 1 يضع نقطة الإدراج قبل العنصر الأخير.
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5 12 10 15 17 20
مثال 9:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.erase(v.begin()); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.erase(v.begin());: إزالة العنصر الأول من المتجه v باستخدام طريقة المسح (). بما أن v.begin() يشير إلى العنصر الأول، فإن هذه العملية تزيل هذا العنصر من المتجه.
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه المعدل (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
10 15 20
مثال 10:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.erase(v.begin()+1, v.end()); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.erase(v.begin() + 1, v.end());: إزالة العناصر من العنصر الثاني (index 1) حتى نهاية المتجه باستخدام طريقة المسح () مع التكرارات التي تحدد النطاق. يتم تحديد النطاق من v.begin() + 1 (المكرر الذي يشير إلى العنصر الثاني) إلى v.end() (المكرر الذي يشير إلى العنصر الأخير).
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة المتبقية باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه المعدل (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير المتبقي (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر المتبقي الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5
مثال 11:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v(10); for (int i = 0; i <10; i++) v[i] = i; cout << "Vector size initially: " << v.size(); cout << "\nVector elemens are: "; for (int i = 0; i <10; i++) cout << v[i] << " "; v.resize(5); cout << "\n\nVector size after resize(5): " << v.size(); v.shrink_to_fit(); return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v(10);: تهيئة المتجه v بالحجم 10، والذي يحتوي على عناصر تمت تهيئتها افتراضيًا (تتم تهيئة جميع العناصر إلى الصفر في هذه الحالة).
- يعين القيم لعناصر المتجه باستخدام حلقة.
- يُخرج الحجم الأولي للمتجه باستخدام v.size().
- إخراج عناصر المتجه باستخدام حلقة.
- يتم تغيير حجم المتجه إلى الحجم 5 باستخدام v.resize(5). تعمل هذه العملية على تقليل حجم المتجه إلى 5، وتتم إزالة أي عناصر زائدة من المتجه.
- إخراج حجم المتجه بعد تغيير الحجم.
- يستدعي v.shrink_to_fit() لتقليل قدرة المتجه ليتناسب مع حجمه.
ملحوظة:Shrink_to_fit() هو طلب غير ملزم لتقليل سعة المتجه ليناسب حجمه ولكنه لا يضمن أن السعة سيتم تقليلها فعليًا. قد يختار التنفيذ تجاهل الطلب.