stream שחי בתוך מחרוזת
אתם כבר מכירים את cin ואת cout: streams שמחוברים למקלדת ולמסך. string stream הוא אותו רעיון, רק שהנתונים חיים ב-std::string בזיכרון. כותבים לתוכו עם << ומוציאים ממנו ערכים עם >>, בדיוק כמו ב-streams של הקונסולה, אבל שום דבר לא נוגע בטרמינל.
הרעיון היחיד הזה פותר שתי בעיות יומיומיות: פענוח טקסט לערכים עם טיפוסים, ובנייה של מחרוזת מעוצבת מחלקים מעורבים. הכול נמצא בקובץ הכותרת <sstream> ומגיע בשלושה סוגים:
istringstream: קריאה בלבד, לפענוח קלט.ostringstream: כתיבה בלבד, לבניית פלט.stringstream: שני הכיוונים.
ss.str() מחזיר את כל מה שנכתב עד עכשיו כ-string רגיל. אופרטורי ה-<< ביצעו את אותו עיצוב של int לטקסט ש-cout מבצע: פשוט תפסתם את התוצאה במקום להדפיס אותה.
פענוח: הוצאת ערכים עם טיפוסים
הכוח האמיתי מתגלה עם >>. תנו ל-stream טקסט, והחילוץ ממיר כל יחידה לטיפוס שביקשתם, תוך דילוג על הרווחים ביניהן. זו הדרך הנקייה לפרק שורה אחת לכמה שדות עם טיפוסים:
כל >> קורא עד הרווח הבא וממיר: את "Ada" ל-string, את "36" ל-int, ואת "5.5" ל-double. שימו לב ל-istringstream כאן: הטיפוס שמיועד לקלט בלבד מבהיר שאתם רק מפענחים.
המרת מחרוזות למספרים (בבטחה)
משימה נפוצה היא להפוך מחרוזת בודדת כמו "42" ל-int. stringstream עושה את זה, וגם אומר לכם אם הטקסט באמת היה מספר תקין, דבר ש-atoi אף פעם לא עושה:
כאן ss >> value קורא 123, עוצר ב-a ומצליח, ולכן אם זה חשוב, בדקו תמיד את המחרוזת כולה. בדיקה עמידה היא לקרוא את המספר ואז לוודא שלא נשאר שום דבר משמעותי. להמרות פשוטות של ערך בודד, std::stoi, std::stod וחבריהם קצרים יותר. השתמשו ב-stringstream כשמחרוזת אחת נושאת כמה ערכים מעורבים.
פיצול מחרוזת לפי תו מפריד
פיצול טקסט הוא המשימה הכי מחופשת בגוגל בנושא string streams. שלבו stream עם std::getline, שיודע לקרוא עד כל תו מפריד, לא רק עד שורה חדשה:
התבנית while (getline(...)) רצה בלולאה עד שה-stream מתרוקן, כי getline מחזיר את ה-stream וה-stream נבדק כ-false ברגע שלא נשאר כלום. כל יחידה נדחפת לvector, המקום הטבעי לאסוף מספר משתנה של חלקים מפוצלים. הסירו את הארגומנט השלישי ותפצלו במקום זאת לפי רווחים עם >>: בחרו את התו המפריד שהנתונים שלכם משתמשים בו.
בניית מחרוזות בזיכרון
בכיוון ההפוך, ostringstream מרכיב מחרוזת מעוצבת בלי סכנת גלישת החוצץ של sprintf ובלי שרשור ידני שביר. הוא מושלם לשורות לוג, לשמות קבצים או להודעות שנתפרות ממספרים וטקסט:
כל העיצוב של <iomanip> שהייתם משתמשים בו עם cout, כמו setw, setfill, setprecision ו-hex, עובד באותה צורה על string stream, כך שמקבלים פלט מרופד, ברוחב קבוע או בהקסדצימלי שנתפס ישירות לתוך string.
המלכודת: לאפס לפני שימוש חוזר
המלכודת שנושכת את כולם היא שימוש חוזר ב-stream בלי לאפס אותו. ברגע ש-stream מגיע לסוף הנתונים שלו, הדגלים eof ו-fail שלו ננעלים במצב דלוק, וכל >> מאוחר יותר בשקט לא עושה כלום: בלי שגיאה, רק ערכים ישנים:
stringstream ss("10");
int a, b;
ss >> a; // a = 10, ה-stream בסוף -> דגל eof דלוק
ss.str("20"); // טוענים טקסט חדש...
ss >> b; // נכשל בשקט: דגלי השגיאה עדיין דלוקים, b לא השתנה
חייבים לנקות גם את מצב השגיאה וגם את התוכן. הסדר הנכון הוא clear() קודם, ואז str():
שתי טעויות קשורות: ss.clear() מאפס את הדגלים אבל לא מרוקן את החוצץ (לשם כך השתמשו ב-ss.str("")), ו-stringstream שממשיכים להוסיף אליו עם << ימשיך לגדול: צרו אחד חדש בתוך הלולאה במקום להשתמש מחדש באחד מלוכלך בכל איטרציה.
הבא בתור: מערכים
string streams נתנו לכם דרך נקייה להפוך טקסט לערכים עם טיפוסים ובחזרה, ולעיתים קרובות רשימה שלמה שלהם, כמו ה-vector שמילאתם בזמן פיצול ה-CSV. כדי לשמור אוספים קבועים של ערכים כאלה ולגשת אליהם לפי אינדקס, צריך את המכולה הבסיסית ביותר בשפה. בהמשך נעבור על מערכים: איך מצהירים עליהם, ניגשים אליהם לפי אינדקס, עוברים עליהם בלולאה, ונמנעים מגישה מחוץ לגבולות שגורמת לכל כך הרבה התנהגות לא מוגדרת ב-C++.
שאלות נפוצות
מהו stringstream ב-C++?
std::stringstream הוא stream שמגובה ב-string במקום במקלדת או בקובץ. כותבים אליו עם << וקוראים ממנו עם >>, בדיוק כמו cout ו-cin, וזה הופך אותו לכלי הסטנדרטי לפענוח טקסט ולבניית מחרוזות בזיכרון. הוא נמצא בקובץ הכותרת <sstream>.
איך ממירים string ל-int ב-C++ עם stringstream?
הכניסו את הטקסט ל-stream וחלצו ממנו למספר: stringstream ss("42"); int n; ss >> n;. בדקו if (ss) (או if (ss >> n)) כדי לוודא שההמרה באמת הצליחה. למקרים פשוטים std::stoi קצר יותר, אבל stringstream זוהר כשמחרוזת אחת מחזיקה כמה ערכים מעורבים.
למה צריך לקרוא ל-clear() על stringstream?
ברגע ש-stream מגיע לסוף הנתונים שלו, הסיביות eof/fail שלו נשארות דלוקות וכל >> נוסף בשקט לא עושה כלום. אם משתמשים שוב באותו stringstream עם תוכן חדש, קראו ל-ss.clear() כדי לאפס את דגלי השגיאה, ואז ל-ss.str(newText) כדי לטעון נתונים חדשים: אחרת הקריאות נכשלות בשקט.