ملخص
يفتتح هذا الدرس الوحدة السادسة من الدورة، وهي مخصّصة للبيانات المركبة. ويشرح الغاية من الوحدة: أن تعلّم تمثيل مجموعات البيانات (تسلسلات ثابتة الحجم ومتغيرة الحجم، وجداول وسجلات) ومعالجتها بالخوارزميات الأساسية، مكتوبةً باليد ومأخوذةً من المكتبة، مع تقدير كلفتها؛ ويقدّم دروسها الستة والخيط الذي يجمعها؛ ويذكّر بما تفترض الوحدة أنه معلوم، ولا سيما الحلقات ذات اللامتغير والتمرير بالمرجع الثابت؛ ويصوغ المسألة التي تحلّها الوحدة، وهي مسألة برنامج عليه أن يحفظ بيانات لا يعرف عددها، وإذا كُتب بمتغيرات منفردة سكت عمّا يزيد عليه منها؛ ويستبق الكلفة بوصفها مقياسًا ثانيًا للخوارزمية إلى جانب صحتها؛ ويبيّن إلى أين تقود، في الوحدات اللاحقة وفي دورة البرمجة بلغة C++ II.
أهداف التعلم
- وصف تسلسل محتويات الوحدة وشرح ما يقدّمه كل درس للدرس الذي يليه.
- تذكّر الحلقات ولامتغيراتها وتمرير الوسائط (بالقيمة وبالمرجع وبالمرجع الثابت)، ومراجعتها عند الحاجة.
- استباق كلفة الخوارزمية بمثال، بوصفها مقياسًا ثانيًا لجودتها إلى جانب صحتها.
- التعرّف على ما يُنتظر إتقانه عند إنهاء الوحدة، وتمييزه ممّا سيُتعلَّم في الوحدات اللاحقة.
تقييم الدخول
قبل اجتياز الوحدة يحسن قياس نقطة البداية. وهذا التقييم تقييم ذاتي: لا تُمنح عليه درجة، وغايته الوحيدة توجيه الدراسة. وتتناول الأسئلة الأربعة الأولى ما تفترض الوحدة أنه معلوم: لامتغير الحلقة التي تبحث عن قيمة عظمى، وطريقة تمرير وسيط كبير، وعدد جولات حلقتين متداخلتين، والحساب عديم الإشارة. وتتناول الأسئلة الستة التي تليها، بترتيب الدروس، الأفكار المحورية في الوحدة: الفهارس الصالحة لتسلسل ما، وإنشاء متجه، وعدد المرات التي يمكن فيها قسمة حجم على اثنين، والبحث في بيانات مرتّبة، والفهرس المزدوج للجدول، وخطر حفظ بيانات الكيان الواحد في مجموعات منفصلة. ولا أحد ملزم بالإصابة فيها بعدُ، وفي بعضها يكون «لا أعرف بعدُ» جوابًا صادقًا: فكل جواب، صائبًا كان أو خاطئًا، يشرح المسألة ويسمّي الدرس الذي يعالجها. وتُقاس المهارات نفسها مرة أخرى في النهاية، في درس الخلاصة.
قبل أن تبدأ الوحدة، قِس نقطة انطلاقك. إنه تقييم ذاتي قصير: لا يُمنح عليه درجة، وتُحفظ نتيجته في متصفحك فقط.
السؤال 1من وحدات سابقةينبغي للمقطع
int largest{0}; int t{}; while (std::cin >> t) { if (t > largest) { largest = t; } }أن يترك فيlargestأكبر درجات الحرارة المقروءة. مع أيّ مدخلات يخفق؟تقارن الجولة الأولى القيمة بالصفر وتحفظها إن كانت موجبة: مع قيمة موجبة واحدة تكون النتيجة صحيحة. ويحسن تتبّع الحلقة قبل الإجابة.مع>لا تحلّ القيمة المساوية للعظمى محلّها، لكن القيمة العظمى هي العدد نفسه: التكرار لا يغيّر النتيجة.يكشف عن عدم مراجعة التهيئة في ضوء اللامتغير: فالحلقة الصحيحة تقتضي أن يصحّ اللامتغير قبل الجولة الأولى.اللامتغير المنشود، «largestهو أكبر ما قُرئ حتى الآن»، لا يصحّ عند البدء، لأنه لا توجد قيمة عظمى قبل قراءة أي شيء، والصفر الابتدائي يغلب كل قيمة سالبة. ويُصحَّح ذلك بالتهيئة بأول قيمة، بعد التحقق من وجودها. ويفعل درس المتجهات الشيء نفسه على تسلسل محفوظ، وتعبّر المكتبة عنه بـstd::ranges::max، وشرطها المسبق ألّا يكون التسلسل فارغًا.راجِع: تصميم حلقات صحيحة
السؤال 2من وحدات سابقةدالة تحسب عدد حروف العلة في نص من مليون محرف، محفوظ في
std::string، ولا تعدّله. كيف يحسن التصريح بوسيطها؟التمرير بالقيمة ينسخ المليون محرف في كل استدعاء: هو صحيح، لكنه يدفع كلفة لا تضيف شيئًا.لا ينسخ، لكنه يسمح بتعديل النص دون حاجة ولا يقبل قيمة حرفية ولا كائنًا مؤقتًا: يخلط بين «عدم النسخ» و«السماح بالتعديل».طريقة التمرير جزء من الواجهة: التمرير بالقيمة يعد الدالة بنسخة خاصة بها، وعلى البرنامج أن يصنعها.يقرأ المرجع الثابت النص دون نسخه ودون إذن بتعديله، ويقبل فوق ذلك كائنًا مؤقتًا. وفي الوحدة 6 يصير هذا الاختيار القاعدة: فالدالة التي تقرأ متجهًا من مليون قيمة تستقبله بوصفهconst std::vector<int>&، ويغدو النسخ غير الضروري أكثر أخطاء الكلفة شيوعًا.راجِع: الوسائط ونطاق الرؤية
السؤال 3من وحدات سابقةكم مرة تُنفَّذ
++countفيfor (int i{0}; i < 6; ++i) { for (int j{0}; j < i; ++j) { ++count; } }؟الإجابة الصحيحة:
تدور الحلقة الداخلية 0 جولة مع ، وجولة واحدة مع ، وهكذا حتى 5 جولات مع : والمجموع ، وهو . ويعمّم درس كلفة الخوارزمية هذا العدّ: حلقتان تمرّان على كل الأزواج من قيمة تدوران جولة، وهذا العدد يقرّر هل تصلح الخوارزمية لمليون قيمة.راجِع: الحلقات
السؤال 4من وحدات سابقةماذا تكتب
std::println("{}", 0u - 1);في نظام يكون فيهunsigned intبطول 32 بتًا؟يظنّ أن النوع عديم الإشارة يقبل القيم السالبة.يظنّ أن الطرح يتوقف عند أصغر قيمة في النوع؛ والحساب عديم الإشارة يلتفّ ولا يتشبّع.يخلط بين تحذير محتمل وخطأ: التعبير صالح وقيمته معرَّفة.الحساب عديم الإشارة حساب بترديد: طرح 1 من 0 يلتفّ إلى أكبر قيمة في النوع، . وتقيس الوحدة 6 الأحجام بـstd::size_t، وهو أيضًا عديم الإشارة، فلا يعطيv.size() - 1مع متجه فارغ ، بل عددًا هائلًا؛ ويحذّر درس المصفوفات من هذا الطرح، ويعرض درس المتجهات الحلقة التي تخرج بسببه عن الحدود.راجِع: الأعداد الصحيحة وتمثيلها
السؤال 5في تسلسل من عنصرًا مرقّمة كما في C++، أيّ الفهارس تعيّن عنصرًا؟
يرقّم من الواحد، كما في اللغة الدارجة؛ وفي C++ يكونa[n]خارج التسلسل أصلًا.هو أكثر أخطاء الفرق بواحد شيوعًا: يعدّ فهرسًا لـ عنصرًا.هذا جواب صادق: الدرس 6.1 يشرحه، مع السبب الذي من أجله يبدأ العدّ من الصفر.السؤال 6ماذا يحوي
vبعدstd::vector<int> v(3, 7);؟يقرأ الأقواس الهلالية كأنها أقواس معقوصة.يعكس دور العددين.هذا جواب صادق: الدرس 6.2 يقدّم المتجه وطرق إنشائه.السؤال 7كم جولة تدور
for (int m{1024}; m > 1; m /= 2) { ... }؟الإجابة الصحيحة:
قيمmعند بدء كل جولة هي 1024 و512 و256 و128 و64 و32 و16 و8 و4 و2: عشر جولات، لأن ، والعشرة هي . والخوارزمية التي تختزل مسألتها إلى النصف في كل خطوة تقوم بما يقارب خطوة، ويسمّيها درس كلفة الخوارزمية لوغاريتمية: مع ألف مليون قيمة يكفيها نحو ثلاثين.راجِع: كلفة الخوارزمية
السؤال 8في دفتر هاتف مليون اسم مرتّبة ترتيبًا أبجديًا. للعثور على أحدها يُفتح الدفتر من المنتصف، ويُستبعد النصف الذي لا يمكن أن يكون الاسم فيه، ويُكرَّر ذلك مع النصف الآخر. كم مرة، على الأكثر، يجب فتحه؟
هذا عدّ البحث الذي يفحص الأسماء واحدًا واحدًا: من دون الإفادة من الترتيب يُجتاز في المتوسط نصف الدفتر.هذا الجذر التربيعي للمليون: يخلط بين قسمة الجزء إلى نصفين وقسمته إلى كتل.يستعمل اللوغاريتم العشري: كل فتحة تستبعد النصف، لا تسعة أعشار.كل فتحة تختزل إلى النصف الجزء الذي قد يكون الاسم فيه، و يتجاوز المليون: بعد عشرين تنصيفًا يبقى اسم واحد. هذا هو البحث الثنائي في درس البحث والترتيب، الذي لا يعمل إلا إذا كانت البيانات مرتّبة، ويُسوَّغ بلامتغير.راجِع: البحث والترتيب
السؤال 9مع
std::vector<std::vector<int>> t{{1, 2, 3}, {4, 5, 6}};، كم تساويt[1][0]وt.size()؟يقرأ الفهرس الأول على أنه عمود ويعدّ الأعمدة بدل الصفوف.يظنّ أنsize()يعدّ كل خلايا الجدول.هذا جواب صادق: الدرس 6.5 يقدّم الجداول بوصفها متجهات من المتجهات.السؤال 10يحفظ برنامج أسماء أفراد مجموعة في متجه وعلاماتهم في متجه آخر، بحيث تكون العلامة
grades[i]علامةnames[i]. إذا رتّب متجه العلامات وحده، فماذا يحدث؟المتجهان عند المترجم مستقلان: العلاقة موجودة فقط في ذهن من كتب البرنامج.تستقبل الخوارزمية متجهًا واحدًا ولا تعلم بوجود الآخر.كل وصول صالح وكل نتيجة معرَّفة: العيب منطقي، ولذلك لا يشير إليه أيّ من كاشفَي الدورة (كاشف أخطاء الذاكرة، AddressSanitizer، وكاشف السلوك غير المعرَّف، UndefinedBehaviorSanitizer).
جارٍ تحميل المحتوى…