Menu

לולאת for מבוססת טווח ב-C++: תחביר, auto והפניות

לולאת for מבוססת טווח ב-C++ בהסבר ברור: מעבר נקי על מערכים, vectors, מחרוזות ו-maps, למה כדאי להשתמש ב-auto& וב-const auto&, ומלכודות ההעתקה וביטול האיטרטורים שכדאי להימנע מהן.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

"לכל איבר, עשו את זה"

לולאת ה-for הקלאסית שסופרת מצוינת כשיש אינדקס שמניע אותה. אבל ברוב הזמן האינדקס בכלל לא מעניין אתכם: אתם רק רוצים לגעת בכל איבר של מכולה. כתיבת for (int i = 0; i < v.size(); i++) בשביל זה רועשת, ו-i נמצא במרחק טעות אחת של אחד-יותר-מדי מקריאה מחוץ לגבולות.

C++11 הוסיפה את לולאת for מבוססת הטווח בדיוק בשביל זה. נותנים שם למשתנה, מצביעים על מכולה, והלולאה עוברת בשבילכם על כל איבר:

בלי אינדקס, בלי .size(), בלי גבולות לטעות בהם. קראו את זה כ"לכל s ב-scores". זה עובד על מערכים גולמיים, std::vector, std::string, std::map וכל דבר אחר שחושף begin() ו-end().

תנו ל-auto לבחור את הטיפוס

כתיבה מפורשת של טיפוס האיבר עובדת, אבל היא שבירה: שנו את הטיפוס של המכולה וכל לולאה תצטרך להשתנות גם היא. שלבו את ה-for מבוסס הטווח עם auto והקומפיילר יסיק בשבילכם את טיפוס האיבר:

אבל יש כאן עלות נסתרת. auto name רגיל מסיק string ומעתיק כל איבר לתוך name בכל מעבר. עבור int זה בחינם, עבור string או struct גדול זו הקצאה מבוזבזת בכל איטרציה. הפתרון הוא הפניות, וזה הדבר הבא שצריך להבין.

שינוי במקום עם auto&

אם כותבים auto x, מקבלים עותק, ולכן השמה ל-x משנה את העותק ולא את המכולה. שימו לב למלכודת הזו:

ההכפלה בשקט לא עושה כלום, כי n הוא עותק זמני. כדי באמת לערוך את האיברים, קחו אותם לפי הפניה עם auto&:

ה-& הבודד הוא כל ההבדל בין "להסתכל בלי לגעת" לבין "לערוך במקום". אם אי פעם תתהו למה השינויים שלכם נעלמים, כמעט תמיד זו הסיבה.

קריאה בלי העתקה: const auto&

כשצריך רק לקרוא איברים אבל יקר להעתיק אותם, השתמשו ב-const auto&. ההפניה חוסכת את ההעתקה, ו-const מתעד (ואוכף) שלא תשנו כלום:

כלל אצבע טוב:

for (auto x : c)         // עותק: טיפוסים זולים (int, char, מצביעים)
for (auto& x : c)        // עריכה: רוצים לשנות את האיברים
for (const auto& x : c)  // קריאה: טיפוסים כבדים שרק בודקים

השתמשו כברירת מחדל ב-const auto& לקריאה וב-auto& לכתיבה. השתמשו ב-auto רגיל רק לטיפוסים קטנים באמת שזול להעתיק.

לולאה על maps וזוגות

לולאת for מבוססת טווח על std::map נותנת לכם std::pair לכל רשומה, עם .first (המפתח) ו-.second (הערך). מאז C++17, structured bindings מאפשרים לפרק את הזוג הזה לשני משתנים עם שמות ישירות בכותרת הלולאה:

[name, age] ברור הרבה יותר מחזרה על entry.first ו-entry.second בכל מקום. השאירו גם כאן את const auto&: המפתח של רשומה ב-map הוא string, ולכן העתקה של כל זוג תהיה בזבזנית.

המלכודת: אל תשנו גודל תוך כדי לולאה

המלכודת הגדולה ביותר היא שינוי הגודל של המכולה בזמן שלולאת for מבוססת טווח עוברת עליה. קריאה ל-push_back, erase, insert או clear עלולה להקצות מחדש את האחסון שמתחת ולבטל את האיטרטורים הפנימיים של הלולאה. התוצאה היא התנהגות לא מוגדרת, כלומר קריסות או זבל, לא שגיאה ידידותית:

vector<int> v = {1, 2, 3};
for (int x : v) {
    v.push_back(x);   // התנהגות לא מוגדרת: ההקצאה מחדש מבטלת את הטווח
}

אם צריך להוסיף או להסיר איברים תוך כדי עיבוד, עברו ללולאת for מבוססת אינדקס או איטרטור ונהלו את הגבולות בעצמכם, או בנו מכולת תוצאה נפרדת והחליפו אליה אחר כך. שתי מלכודות קטנות יותר מאותה משפחה: לעולם אל תקשרו לולאת for מבוססת טווח לאובייקט זמני שמת מיד (for (auto x : makeVector()) בסדר, אבל for (auto& x : someObj.getTempVector()) עלול להישאר תלוי), וזכרו ש-for (auto& c : myString) מאפשר לשנות תווים בודדים במקומם.

הבא בתור: פונקציות

לולאת for מבוססת טווח מסדרת את המעבר על איברים, והבחירות בין auto / auto& / const auto& שלמדתם עכשיו עוברות ישירות לאחד הכלים החשובים ביותר ב-C++. בהמשך נארוז לוגיקה בפונקציות לשימוש חוזר: ניתן לקוד שם, פרמטרים וערך החזרה, כדי שתוכלו לקרוא לו מכל מקום במקום לחזור על עצמכם.

שאלות נפוצות

מהי לולאת for מבוססת טווח ב-C++?

לולאת for מבוססת טווח מבקרת בכל איבר של מכולה (מערך, vector, string, map וכן הלאה) בלי שתצטרכו לנהל אינדקס או איטרטור. התחביר הוא for (auto x : container) { ... }. היא נוספה ב-C++11, והיא הדרך הנקייה ביותר לומר "עשו את זה לכל איבר".

מתי להשתמש ב-auto& במקום auto בלולאת for מבוססת טווח?

השתמשו ב-auto& x כשרוצים לשנות את האיברים במקומם, וב-const auto& x כשרק קוראים אותם אבל רוצים להימנע מהעתקה (חשוב ל-string, ל-vector או לאובייקטים גדולים). auto x רגיל יוצר עותק בכל איטרציה: בסדר לטיפוסים זולים כמו int, בזבזני בכל מקרה אחר.

האם אפשר לשנות את הגודל של vector בתוך לולאת for מבוססת טווח ב-C++?

לא. קריאה ל-push_back, erase, insert או clear על המכולה שעוברים עליה מבטלת את האיטרטורים הפנימיים של הלולאה והיא התנהגות לא מוגדרת: היא עלולה לקרוס או להשחית נתונים בשקט. אם צריך להוסיף או להסיר איברים תוך כדי לולאה, השתמשו במקום זאת בלולאת for מבוססת אינדקס או איטרטור.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל