איך C++ מדברת עם הקונסולה
C++ מבצעת קלט ופלט בקונסולה דרך streams מקובץ הכותרת <iostream>. כותבים החוצה עם cout (פלט תווים) וקוראים מהמשתמש עם cin (קלט תווים). הם משתמשים בשני אופרטורים שכבר ראיתם כהזזות סיביות, שמקבלים כאן תפקיד חדש:
<<הוא אופרטור ההכנסה: הוא שולח נתונים לתוךcout.>>הוא אופרטור החילוץ: הוא מוציא נתונים מתוךcinלתוך משתנה.
דרך נוחה לזכור את הכיוון: החצים מצביעים לכיוון שבו הנתונים זורמים. עכשיו, כשאתם יודעים לשמור טקסט במחרוזות, בואו נכניס ונוציא את הטקסט הזה מהתוכנית.
הדפסה עם cout
cout שולח לפלט הסטנדרטי כל מה שמכניסים אליו. אפשר לשרשר כמה << בפקודה אחת ולערבב בחופשיות טקסט, מספרים ומשתנים.
כל << מוסיף לאותה שורה עד שמכניסים תו שורה חדשה. יש שתי דרכים לעשות את זה: '\n' מכניס תו שורה חדשה, ו-endl מכניס שורה חדשה וגם מרוקן את החוצץ אל המסך. לריקון יש מחיר אמיתי, ולכן בתוך לולאה שמדפיסה אלפי שורות העדיפו '\n': ה-stream מתרוקן בעצמו כשצריך (ותמיד ביציאה מהתוכנית).
// מתאים להודעה חד-פעמית:
cout << "Done" << endl;
// בלולאה חמה העדיפו את זה, בלי ריקון כפוי בכל איטרציה:
for (int i = 0; i < 1000000; ++i)
cout << i << '\n';
קריאה עם cin והאופרטור >>
cin >> variable קורא יחידה אחת שמופרדת ברווחים וממיר אותה לטיפוס של המשתנה. הוא מדלג על רווחים או שורות חדשות בהתחלה, ואז עוצר ברווח הבא.
מכיוון ש->> עוצר ברווחים, הוא מצוין למספרים בודדים ולמילים בודדות אבל חסר תועלת למשפט שלם: cin >> word על הקלט hello world קורא רק את hello ומשאיר את world בחוצץ לקריאה הבאה.
קריאת שורה שלמה עם getline
כדי לתפוס שורה שלמה, כולל כל הרווחים, השתמשו ב-getline(cin, line), שקורא הכול עד מקש Enter לתוך std::string.
זה הכלי הנכון בכל פעם שהקלט עשוי להכיל רווחים: שמות, כתובות, משפטים. יש רק מלכודת אחת שמחכה לכם ברגע שמשלבים getline עם >>.
עמוד ה-getline מסביר את הפונקציה בפני עצמה: תווי הפרדה מותאמים, קריאה עד סוף הקלט וקריאת קובץ שורה אחר שורה עם std::ifstream.
מלכודת השורה החדשה של cin ו-getline
זה באג הקלט והפלט הנפוץ ביותר ב-C++. כשכותבים cin >> n, החילוץ קורא את המספר אבל משאיר את תו השורה החדשה (מקש ה-Enter שלחצתם) בחוצץ הקלט. ה-getline הבא רואה מיד את תו השורה החדשה שנשאר, מתייחס לשורה כאילו כבר הסתיימה ומחזיר לכם מחרוזת ריקה, בלי לעצור בכלל כדי לחכות לקלט.
int age;
string city;
cin >> age; // מקלידים 30 ולוחצים Enter; '\n' נשאר בחוצץ
getline(cin, city); // קורא את ה-'\n' שנשאר -> city הוא "" (ריק!)
הפתרון הוא להשליך את תו השורה החדשה שנשאר בעזרת cin.ignore אחרי ה->> ולפני ה-getline:
cin.ignore(numeric_limits<streamsize>::max(), '\n') מדלג על תווים עד שהוא בולע תו שורה חדשה (או מגיע לסוף הקלט). זו הגרסה העמידה: cin.ignore() הקצר משליך תו אחד בלבד, וזה נשבר אם המשתמש הקליד רווחים נוספים אחרי המספר. התרגלו להשתמש בצורה המלאה.
כשהקלט נכשל
אם המשתמש מקליד אותיות במקום שבו ציפיתם למספר, החילוץ נכשל: cin נכנס למצב שגיאה ומשתנה היעד לא מתעדכן בערך מהקלט (מאז C++11 הוא מקבל 0). גרוע מזה, ברגע ש-cin במצב כישלון, כל קריאה מאוחרת יותר מדולגת גם היא בשקט, כך שאפשר להיתקע בלולאה אינסופית.
ההתאוששות היא תמיד שני שלבים: cin.clear() מאפס את דגלי השגיאה כך שאפשר שוב להשתמש ב-stream, ו-cin.ignore(...) משליך את התווים הבעייתיים שעדיין תקועים בחוצץ. אם מדלגים על ה-ignore, הקלט הגרוע נשאר במקומו וה->> הבא נכשל שוב: הלולאה האינסופית הקלאסית. בדיקה של cin >> n ישירות בתנאי עובדת כי ה-stream מומר ל-false כשהוא במצב כישלון.
טעויות נפוצות שכדאי להימנע מהן
- שימוש ב-
cin >> sלמשפט. הוא עוצר ברווח הראשון. השתמשו ב-getlineלכל דבר שיש בו רווחים. - שכחה של
cin.ignoreבין>>ל-getline. תו השורה החדשה שנשאר נותן לכם שורה ריקה. נקו את החוצץ קודם. - שימוש ב-
endlבכל מקום. כל אחד כופה ריקון. השתמשו כברירת מחדל ב-'\n'ושמרו אתendlלמקרים שבהם הפלט באמת חייב להופיע עכשיו. - התעלמות מ-
cinשנכשל. אותיות בקריאה מספרית משאירות אתcinשבור: תמידclear()ואזignore()לפני שקוראים שוב.
שאלות נפוצות
למה getline מדלג על הקלט מיד אחרי שימוש ב-cin ב-C++?
cin >> x קורא את המספר אבל משאיר בחוצץ את תו השורה החדשה שנוצר כשלחצתם Enter. ה-getline הבא קורא עד לתו השורה החדשה שנשאר ומחזיר מיד מחרוזת ריקה. נקו אותו קודם עם cin.ignore(numeric_limits<streamsize>::max(), '\n'); אחרי ה->> ולפני ה-getline.
מה ההבדל בין endl ל-\n ב-C++?
שניהם מסיימים את השורה, אבל endl גם מרוקן (flush) את חוצץ הפלט אל המסך, בעוד ש-'\n' רק מכניס תו שורה חדשה. לריקון יש מחיר: בלולאה צפופה העדיפו '\n' ותנו ל-stream להתרוקן בעצמו. השתמשו ב-endl רק כשבאמת צריך שהפלט יופיע ברגע זה.
איך קוראים שורת טקסט שלמה כולל רווחים ב-C++?
השתמשו ב-getline(cin, line), לא ב-cin >> line. האופרטור >> עוצר ברווח הראשון, ולכן הוא תופס מילה אחת בלבד. getline קורא הכול עד מקש Enter לתוך std::string, כולל רווחים.