Menu

getline ב-C++: קריאת שורה שלמה, תיקון הקלט שמדולג וקריאת קבצים

איך std::getline קורא שורה שלמה לתוך std::string: שימוש עם cin, למה הוא מחזיר מחרוזת ריקה אחרי cin >>, התיקונים עם cin.ignore ו-std::ws, תווי הפרדה מותאמים, וקריאת קובץ שורה אחרי שורה עד סוף הקובץ.

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

קריאת שורה שלמה

cin >> text קורא מילה אחת: הוא נעצר ברווח הראשון. כדי לקרוא את כל מה שהמשתמש הקליד עד מקש Enter, כולל רווחים, השתמשו ב-std::getline מה-header <string>:

#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "Full name: ";
    std::getline(std::cin, name);
    std::cout << "Hello, " << name << "!\n";
}

הקלדה של Ada Lovelace מדפיסה Hello, Ada Lovelace!. עם std::cin >> name הייתה מודפסת Hello, Ada!, ו-Lovelace היה נשאר לחכות בקלט.

החתימה היא:

std::istream& std::getline(std::istream& input, std::string& text);
std::istream& std::getline(std::istream& input, std::string& text, char delim);
  • input הוא כל זרם קלט: std::cin, קובץ שנפתח עם std::ifstream, או std::istringstream שנבנה ממחרוזת.
  • text מוחלף (לא מתווסף אליו) בתווים שנקראו.
  • delim הוא התו שמסיים את הקריאה. ברירת המחדל שלו היא '\n'. תו ההפרדה מוסר מהזרם ולא נשמר ב-text.
  • הערך המוחזר הוא הזרם עצמו, וזה מה שמאפשר לבדוק אותו בתנאי.

מכיוון ש-getline עובד אותו דבר על כל זרם קלט, הדוגמאות שאפשר להריץ בעמוד הזה קוראות מ-std::istringstream שמחזיק טקסט קבוע. כך הפלט צפוי בלי להקליד כלום. כדי לקרוא מהמקלדת במקום, החליפו את משתנה הזרם ב-std::cin.

פלט:

[Ada Lovelace]
[Alan Turing]

הסוגריים המרובעים מראים שתו השורה החדשה אינו חלק מאף אחת מהמחרוזות.

getline אחרי cin >> מחזיר מחרוזת ריקה

זו הבעיה הנפוצה ביותר עם getline. קוראים מספר עם >>, ואז שורה עם getline, ונראה שה-getline מדולג:

פלט:

age: 30
city: []

input >> age קורא את התווים 30 ונעצר בתו הראשון שלא יכול להיות חלק ממספר, תו השורה החדשה. תו השורה החדשה הזה נשאר בזרם. אחר כך getline קורא עד תו השורה החדשה הבא, מוצא אחד מיד ומחזיר מחרוזת ריקה. עם std::cin התוצאה היא שהתוכנית אף פעם לא עוצרת לחכות לתשובה השנייה.

יש שני תיקונים מקובלים. שניהם נכנסים בין ה->> ל-getline.

תיקון 1: לזרוק את שארית השורה עם ignore.

פלט:

age: 30
city: [Paris]

ignore(n, '\n') זורק תווים עד שהוא הסיר תו שורה חדשה או n תווים, המוקדם מביניהם. העברת ה-std::streamsize הגדול ביותר האפשרי אומרת "עד תו השורה החדשה, לא משנה כמה הוא רחוק". זה גם מסיר כל מה שהמשתמש הקליד אחרי המספר באותה שורה, כמו רווחים בסוף. עם המקלדת השורה היא std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');, והיא צריכה #include <limits>.

std::cin.ignore() לבד זורק בדיוק תו אחד. זה עובד כשתו השורה החדשה בא מיד אחרי המספר, ונכשל ברגע שיש רווח לפניו.

תיקון 2: לדלג על רווחים לבנים בהתחלה עם std::ws.

פלט:

age: 30
city: [Paris]

std::ws הוא manipulator שצורך את כל הרווחים הלבנים (רווחים, טאבים, שורות חדשות) במיקום הנוכחי. input >> std::ws מחזיר את הזרם, כך שאפשר להכניס אותו ישר ל-getline. הוא גם מדלג על שורות ריקות ועל רווחים בהתחלה, מה שנוח לשמות ולערים אבל שגוי כשרווחים בהתחלה או שורה ריקה הם קלט משמעותי. במקרה כזה השתמשו ב-ignore.

קריאה עד סוף הקלט

getline מחזיר את הזרם, וזרם מומר ל-false ברגע שקריאה נכשלת. כשאין עוד שורות, getline נכשל, ולכן לולאת while סביבו קוראת כל שורה ונעצרת בסוף:

פלט:

1: [apples 3]
2: [bread 1]
3: []
4: [milk 2]
read 4 lines

שני פרטים מהפלט הזה:

  • שורה ריקה היא עדיין קריאה מוצלחת. היא נותנת מחרוזת ריקה והלולאה ממשיכה. רק הגעה לסוף הקלט עוצרת אותה.
  • השורה האחרונה לא צריכה תו שורה חדשה בסופה. אם הטקסט היה נגמר ב-milk 2 בלי \n, getline עדיין היה מחזיר אותה, והקריאה הבאה הייתה נכשלת.

עם std::cin, אותה לולאה קוראת עד שהקלט נגמר. זה קורה כשהקלט מוזרם מקובץ (./program < data.txt), או כשהמשתמש לוחץ Ctrl+D ב-macOS וב-Linux, או Ctrl+Z ואז Enter ב-Windows.

הימנעו מהדפוס while (!input.eof()) { std::getline(input, line); ... }. eof() הופך לנכון רק אחרי שקריאה כבר הגיעה לסוף, ולכן הגוף רץ פעם נוספת עם קריאה שנכשלה ועם line ישן. שימו את הקריאה ל-getline בתנאי במקום זאת.

קריאת קובץ שורה אחרי שורה

קבצים משתמשים באותה לולאה. פתחו את הקובץ עם std::ifstream מ-<fstream>, בדקו שהוא נפתח, וקראו:

פלט:

1. Ada 92
2. Grace 88
3. Alan 95

if (!file) תופס קובץ חסר או נתיב שגוי לפני הלולאה. בלעדיו, הלולאה פשוט קוראת אפס שורות, וזה נראה כמו קובץ ריק ולא כמו שגיאה. הקובץ נסגר אוטומטית כש-file יוצא מהטווח בסוף main.

קבצים שנשמרו ב-Windows מסיימים כל שורה ב-\r\n. כשקובץ כזה נקרא ב-Linux או ב-macOS, getline מסיר את ה-\n אבל משאיר את ה-\r, כך שכל שורה נגמרת בתו חזרת גררה בלתי נראה ששובר השוואות כמו line == "done". הסירו אותו אחרי הקריאה:

if (!line.empty() && line.back() == '\r') {
    line.pop_back();
}

שימוש בתו הפרדה מותאם

הארגומנט השלישי משנה את התו שמסיים כל קריאה. שימוש נפוץ הוא פיצול של שורה אחת לפי פסיקים. קראו קודם את השורה, ואז פצלו אותה דרך std::istringstream:

פלט:

fields: 3
[Ada Lovelace]
[36]
[London]

הפסיק מוסר מהזרם ולא נשמר, בדיוק כמו תו השורה החדשה בצורת ברירת המחדל. שימו לב שעם תו הפרדה מותאם, תו שורה חדשה הוא תו רגיל. אם קוראים ל-std::getline(file, field, ',') ישירות על קובץ עם כמה שורות, השדה האחרון של שורה אחת והשדה הראשון של השורה הבאה חוזרים מחוברים יחד, כמו ב-"London\nGrace Hopper". קריאה של שורה אחת בכל פעם ופיצול של כל שורה בנפרד מונעים את זה. העמוד על string streams מסביר את std::istringstream בפירוט רב יותר, כולל המרה של שדות כמו "36" למספרים.

std::getline מול cin.getline

יש גם פונקציה חברה עם שם דומה:

char buffer[20];
std::cin.getline(buffer, 20);        // פונקציה חברה, מערך char בגודל קבוע

std::string text;
std::getline(std::cin, text);        // פונקציה חופשית, std::string

cin.getline כותבת לתוך מערך char ושומרת לכל היותר size - 1 תווים ועוד את ה-'\0' המסיים. שורה ארוכה יותר מכניסה את הזרם למצב כישלון, וכל קריאה מאוחרת יותר מדולגת עד שקוראים ל-std::cin.clear(). ל-std::getline עם std::string אין מגבלת אורך שצריך לנהל. השתמשו בו, אלא אם אתם עובדים עם API שדורש מערך char.

טעויות נפוצות

  • ערבוב של >> ו-getline בלי לנקות את תו השורה החדשה. הוסיפו ignore(...) או השתמשו ב-std::ws ביניהם. העמוד על קלט ופלט מראה את אותה בעיה עם std::cin, ואיך להתאושש כשקריאה של מספר נכשלת.
  • קריאה ל-getline(std::cin, number) עם int. getline קורא רק לתוך std::string. קראו את השורה, ואז המירו עם std::stoi או עם std::istringstream.
  • לולאה על eof(). בדקו את הקריאה ל-getline עצמה: while (std::getline(input, line)).
  • שכחה של #include <string>. std::getline למחרוזות מוצהר ב-<string>. מהדרים מסוימים מעבירים את זה דרך <iostream>, ואז הקוד נכשל על מהדר אחר.
  • לא לבדוק שקובץ נפתח. נתיב שגוי גורם ל-std::ifstream להיכשל בשקט ולולאת הקריאה לרוץ אפס פעמים.

שאלות נפוצות

מה עושה getline ב-C++?

std::getline(stream, text) קורא תווים מ-stream לתוך ה-std::string בשם text עד שהוא מגיע לתו שורה חדשה, ואז מסיר את תו השורה החדשה הזה מהזרם בלי לשמור אותו. בניגוד ל-cin >> text, הוא שומר על רווחים, ולכן הוא קורא שורה שלמה כמו שם מלא או משפט.

למה getline מדולג אחרי cin >> ב-C++?

cin >> number מפסיק לקרוא בתו השורה החדשה שהקלדתם אחרי המספר ומשאיר אותו בבאפר הקלט. ה-getline הבא מוצא אותו מיד ומחזיר מחרוזת ריקה. זרקו אותו קודם עם std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');, או קראו ל-std::getline(std::cin >> std::ws, text) כדי לדלג על רווחים לבנים בהתחלה.

איך משתמשים ב-getline עם תו הפרדה אחר מתו שורה חדשה?

העבירו את תו ההפרדה כארגומנט שלישי: std::getline(stream, field, ','). הוא קורא עד הפסיק הבא, שומר את הטקסט שלפניו וזורק את הפסיק. תווי שורה חדשה נחשבים אז לתווים רגילים, ולכן בדרך כלל משלבים את זה עם std::stringstream שנבנה משורה אחת.

איך קוראים קובץ שורה אחרי שורה ב-C++?

פתחו אותו עם std::ifstream file("data.txt"); ועברו עליו בלולאה עם while (std::getline(file, line)) { ... }. getline מחזיר את הזרם, שמומר ל-false ברגע שאין עוד שורות, ולכן הלולאה נעצרת בסוף הקובץ.

מה ההבדל בין cin.getline ל-getline?

std::getline(std::cin, text) היא פונקציה חופשית שקוראת לתוך std::string, שגדל כדי להכיל כל שורה. std::cin.getline(buffer, size) היא פונקציה חברה שקוראת לתוך מערך char בגודל קבוע ונכשלת אם השורה ארוכה מ-size - 1 תווים. השתמשו ב-std::getline עם std::string, אלא אם יש לכם סיבה להשתמש במערכים גולמיים.

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

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

להתחיל