Как использовать команду getline в С++?

Я пытаюсь превратить команду cout в команду getline на С++.

Это мой код, который я пытаюсь изменить....

for (int count=0; count < numberOfEmployees; count++)
    {
        cout << "Name: ";
        cin >> employees[count].name; 

        cout << "Title: ";
        cin >> employees[count].title;

        cout << "SSNum: ";
        cin >> employees[count].SSNum;

        cout << "Salary: ";
        cin >> employees[count].Salary;

        cout << "Withholding Exemptions: ";
        cin >> employees[count].Withholding_Exemptions; 
    }

Я пытаюсь изменить эту строку: cin >> employees[count].name; и эту строку: cin >> employees[count].title; на getlines. Кто-нибудь может помочь?

Спасибо


person Gvegas222    schedule 03.02.2012    source источник
comment
comment
@R.MartinhoFernandes: этого недостаточно, пробел, оставшийся после предыдущего ввода, немедленно завершит строку. Давайте закроем как обман; это было объяснено ранее.   -  person Ben Voigt    schedule 03.02.2012
comment
@Бен О. Я не думаю, что когда-либо смешивал линейный ввод с обычным вводом, так как я никогда не сталкивался с этим. Вы правы, это, вероятно, то, что здесь не работает.   -  person R. Martinho Fernandes    schedule 03.02.2012


Ответы (4)


Сброс проблем с cin.getline() в C++

Когда вы хотите удалить посторонние символы из входного потока в C++, обычно это происходит из-за того, что вы смешиваете форматированные и неформатированные методы ввода. Отформатированный метод оставит новую строку в потоке, а неформатированный метод воспользуется ею и успешно завершится, но полностью не сможет выполнить то, что вы хотели.

    #include <iostream>  
 int main() {   
std::cout<<"Enter the letter A: ";  
 std::cin.get();   
std::cout<<"Enter the letter B: "; 
  std::cin.get();   
std::cout<<"Too late, you can't type anymore\n";
 }

Часто этот вопрос связан с другим вопросом: как приостановить программу до ее завершения. Использование cin.get() работает только тогда, когда в потоке не осталось символов. В тот момент, когда вы бросаете cin>> foo; в коде вдруг решение не работает. Вам нужно удалить все оставшиеся символы из потока, прежде чем он снова заработает.

Итак, как решить проблему? Хорошей новостью является то, что если вы не хотите быть придирчивым, это так же просто, как цикл: Синтаксис C++ (переключение обычного текста)

    #include <istream>  
 void ignore_line ( std::istream& in ) { 
  char ch;
        while ( in.get ( ch ) && ch != '\n' );
 } 

Этот цикл просто считывает символы до тех пор, пока не будет прочитан конец файла или новая строка. Обычно предполагается, что интерактивный ввод в C++ ориентирован на строки, и вы гарантированно получите чистый буфер после чтения новой строки. Хотя это не так (ввод не обязательно должен быть линейно-ориентированным), он достаточно широко распространен, чтобы мы могли принять его для целей этой ветки.

Так что же не так с этим подходом? Ничего такого. На самом деле, это настолько хорошо, насколько это возможно, если вы не хотите копаться и устранять тонкие проблемы. Но прежде чем мы рассмотрим проблемы, вот альтернативное решение, которое делает то же самое по-другому:

    #include <ios>
    #include <istream>
    #include <limits>   
void ignore_line ( std::istream& in ) {   
in.ignore ( std::numeric_limits<std::streamsize>::max(), '\n' );
 } 

Функция члена ignore std::istream будет читать и отбрасывать до N символов или до разделителя. В приведенном выше примере N представлено наибольшим значением типа данных streamsize, а разделителем является новая строка. Он одинаково хорошо работает только с большим значением (обычно 80): C++ Syntax (Toggle Plain Text) in.ignore ( 80, '\n' ); Однако тип данных streamsize, скорее всего, будет точным представлением размера буфера, используемого потоком, и, скорее всего, будет работать все время. Это решение, которое я рекомендую.

Так что же не так с этим? Есть две заметные проблемы. Первое легко исправить, и оно связано с тем, что istream не очень гибкий. istream на самом деле является typedef для basic_istream. Если вы хотите, чтобы широкий поток работал с ignore_line, вы SOL с istream. Таким образом, хитрость заключается в том, чтобы вместо этого использовать basic_istream‹>:

    #include <ios>
    #include <istream>
    #include <limits>   
template <typename CharT> 
void ignore_line ( std::basic_istream<CharT>& in ) { 
  in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) ); 
}

Теперь ignore_line — это шаблонная функция, которая будет получать тип символов, содержащихся в потоке, из первого аргумента. Вы можете передать любой поток, производный от basic_istream или специализирующийся на нем, и проблема исчезнет. Вместо простого '\n' рекомендуется использовать расширение для литерала, чтобы при необходимости он был должным образом преобразован в более широкий тип. Легко и приятно.

Вторая проблема сложнее. Гораздо сложнее. Это сложнее, потому что стандартные iostreams, кажется, блокируют ваш путь на каждом шагу из-за отсутствия переносимости или нежелательных функций. На самом деле полностью решить проблему невозможно. Проблема в том, что поведение отличается в зависимости от содержимого потока. Например:

    #include <iostream>
    #include <ios>
    #include <istream>
    #include <limits>  
 template <typename CharT> 
void ignore_line ( std::basic_istream<CharT>& in ) { 
  in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );
 }  
 int main() {  
 std::cout<<"First input: "; 
  std::cin.get();  
 std::cout<<"Clearing cin.\n"; 
  std::cin.clear();   
ignore_line ( std::cin ); 
  std::cout<<"All done.\n"; 
} 

Запустите эту программу три раза:

Ввод: "asdf" Вывод: Программа завершается без вашего участия.

Ввод: просто нажмите Enter. Вывод: программа ждет, пока вы еще раз нажмете Enter.

Ввод: сигнал EOF. Вывод: программа ждет, пока вы еще раз нажмете Enter.

Проблема в том, что поток пуст. Если вы сразу же нажмете Enter, в потоке будет помещена новая строка, которая будет использована cin.get. Аналогично с сигнализацией EOF. В этот момент в потоке ничего не остается, и cin.ignore останавливает все, пока вы не наберете больше символов. Это связано с тем, что cin.ignore блокирует чтение. Если нечего читать, он ждет.

Мы хотели бы, чтобы он не блокировал ни один из этих трех случаев. Хорошая новость заключается в том, что библиотека iostream поддерживает некоторые возможные решения. Плохая новость в том, что это тупиковые пути. Вот два распространенных:

Функция-член sync Класс istream поддерживает функцию-член sync. Почему у него такая функция, обсуждается, потому что никто не может договориться о том, что он должен делать. Даже сам Бьерн Страуструп неправильно заявил, что он отбрасывает все символы в потоке:

  #include <iostream>  
 int main() {   
std::cout<<"First input: ";  
 std::cin.get();   
std::cout<<"Clearing cin.\n";  
 std::cin.clear();   
std::cin.sync();   
std::cout<<"All done.\n"; 
} 

Когда это работает, это работает красиво. Плохая новость заключается в том, что стандарт C++ не требует, чтобы синхронизация делала что-то вроде отбрасывания посторонних символов. Это решение не является переносимым.

Функция-член in_avail Следующим шагом является рассмотрение функции-члена in_avail буфера потока istream. На первый взгляд кажется, что эта функция-член сообщит вам, сколько символов в потоке, и если она вернет 0, вы можете воздержаться от вызова ignore:

  #include <iostream>
    #include <ios>
    #include <istream>
    #include <limits>  
 template <typename CharT> 
void ignore_line ( std::basic_istream<CharT>& in ) {
   if ( in.rdbuf()->in_avail() > 0 )
        in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );
 }  
 int main() { 
  std::cout<<"First input: "; 
  std::cin.get();   
std::cout<<"Clearing cin.\n"; 
  std::cin.clear();  
 ignore_line ( std::cin ); 
  std::cout<<"All done.\n";
 }

Как и в случае с синхронизацией, когда это работает, это работает отлично. Но опять же, стандарт поднимает стену, говоря, что in_avail не требуется для точного представления символов в потоке. На самом деле некоторые популярные реализации имеют строго соответствующий in_avail, который всегда возвращает 0. Не очень полезно. Теперь нам нужно проявить творческий подход.

Функция-член возврата

 #include <iostream>
    #include <ios>
    #include <istream>
    #include <limits> 
  template <typename CharT>
 void ignore_line
 ( std::basic_istream<CharT>& in ) { 
  if ( !in.putback ( in.widen ( '\n' ) ) )
        in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );   else
        in.ignore(); }   
int main() 
{   std::cout<<"First input: ";   
std::cin.get();   
std::cout<<"Clearing cin.\n"; 
  std::cin.clear();  
 ignore_line ( std::cin );  
 std::cout<<"All done.\n";
 } 

Это выглядит очень многообещающе, потому что на первый взгляд кажется, что вы можете попытаться отодвинуть новую строку. Если операция завершится ошибкой, последний прочитанный символ не был новой строкой, и вы можете вызвать игнорирование без блокировки. Если операция выполнена успешно, новая строка возвращается, и вы можете удалить ее, игнорируя один символ.

К сожалению, это не работает. putback не требуется, чтобы сделать что-либо предсказуемым, что поднимает вопрос о том, почему он вообще доступен.

Но откат на самом деле приближает нас к решению, которое кажется достаточно правдоподобным, чтобы работать большую часть времени. Вместо того, чтобы полагаться на неудачу возврата или нет, мы можем гарантировать, что последний прочитанный символ будет возвращен, используя функцию-член sungetc буфера потока. Хитрость заключается в том, чтобы удалить последний символ, затем снова прочитать его и проверить на новую строку:

  #include <iostream>
    #include <ios>
    #include <istream>
    #include <limits>  
 template <typename CharT>
 void ignore_line ( std::basic_istream<CharT>& in ) { 
  if ( in.rdbuf()->sungetc() != std::char_traits<CharT>::eof()
        && in.get() != in.widen ( '\n' ) )   {
        in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );  
 } 
}   
int main() {   
std::cout<<"First input: ";  
 std::cin.get();   
std::cout<<"Clearing cin.\n";   
std::cin.clear();   
ignore_line ( std::cin );   
std::cout<<"All done.\n";
 }

Причина, по которой мы используем sungetc вместо unget из istream, заключается в том, что unget возвращает поток, а sungetc возвращает либо символ, который был вытеснен назад, либо EOF. Таким образом, мы можем легче определить, вышла ли функция из строя или нет.

В случае сбоя sungetc будет выполнено одно из следующих условий:

1) Поток находится в состоянии ошибки. 2) Нет персонажей, которых можно разблокировать. 3) Поток не поддерживает удаление символов.

Если sungetc завершится успешно, всегда будет символ для чтения и проверки новой строки. Если этот символ соответствует новой строке, то последний прочитанный символ также был новой строкой, и нам не нужно вызывать игнорировать. Если символ не совпадает, то целая строка не была прочитана, и мы можем безопасно вызывать игнорирование без блокировки.

Если поток находится в состоянии ошибки, это то, с чем должен иметь дело вызывающий код. Если нет символов, которые нужно удалить, то это именно то, для чего предназначено это решение. Но если поток не поддерживает неполучение символов, это проблема. Функция ignore_line всегда не будет отбрасывать символы, поэтому для тех реализаций, которые не поддерживают отмену получения символов, мы можем добавить флаг, который вызывает игнорирование. Иногда полезно также знать, сколько символов было проигнорировано, так что давайте добавим и это, и мы получим окончательное решение:

   #include <ios>
    #include <istream>
    #include <limits>   
template <typename CharT> 
std::streamsize ignore_line (   std::basic_istream<CharT>& in, bool always_discard = false ) { 
  std::streamsize nread = 0;
        if ( always_discard || ( in.rdbuf()->sungetc() != std::char_traits<CharT>::eof()
        && in.get() != in.widen ( '\n' ) ) )  
 {
        // The stream is good, and we haven't
        // read a full line yet, so clear it out
        in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );
        nread = in.gcount();   }
        return nread; 
}

На всякий случай я также включу манипулятор, который вызывает ignore_line, а также манипулятор, который использует ignore_line для приостановки программы. Таким образом, немытые массы могут перестать использовать систему ("ПАУЗА"); и получить();:

  class ignoreline { 
  bool _always_discard;
   mutable std::streamsize _nread; 
public:  
 ignoreline ( bool always_discard = false )
        : _always_discard ( always_discard ), _nread ( 0 )   {}
        std::streamsize gcount() const { return _nread;
 }
        template <typename CharT>  
 friend std::basic_istream<CharT>& operator>> (        std::basic_istream<CharT>& in, const ignoreline& manip )  
 {
        manip._nread = ignore_line ( in, manip._always_discard );
        return in;  
 } 
};  
 class pause { 
  ignoreline _ignore; 
public:   
pause ( bool always_discard = false )        : _ignore ( always_discard )   {}
        std::streamsize gcount() 
const { return _ignore.gcount(); 
}
        template <typename CharT> 
  friend std::basic_istream<CharT>& operator>> (        std::basic_istream<CharT>& in, const pause& manip )   
{
        if ( !( in>> manip._ignore ) )
          in.clear();

        std::cout<<"Press Enter to continue . . .";

        return in.ignore();  
 } 
}; 

Теперь все три случая ведут себя одинаково:

     int main() 
{   std::cout<<"First input: "; 
  std::cin.get();   
std::cout<<"Clearing cin.\n";  
 std::cin>> ignoreline();  
 std::cout<<"All done.\n";  
 std::cin>> pause();
 } 

И мораль этой истории такова: это никогда не бывает так просто, как кажется, написание переносимого кода, который делает то, что вы хотите, чрезвычайно сложно, а библиотека iostream — это огромный беспорядок.

ПРИМЕЧАНИЕ. Если вы новичок, забудьте обо всем и просто поймите, что есть проблема со сбросом, и используйте cin

person Community    schedule 03.02.2012
comment
ПРИМЕЧАНИЕ. Если вы новичок, забудьте все о cin.getline и просто поймите, что есть проблема со сбросом, и используйте cin - person Rohit Vipin Mathews; 03.02.2012
comment
Спасибо за этот ответ! Смешно, что такая повседневная проблема имеет такое сложное решение. - person Dave; 24.10.2014

Взглянув на прототип getline, вы увидите, что вам нужны две вещи:

  1. istream объект
  2. Строка для результата
  3. (необязательно) разделитель «новой строки» (по умолчанию '\n'.

В вашем случае кажется, что разделитель может быть по умолчанию: '\n'; если нет, вы можете указать, какой символ разделяет каждое значение (может быть, это пробел ' ' или что-то в этом роде). Поток cin будет вашим istream объектом, а каждое из свойств employees[count] будет служить вторым параметром. Таким образом, getline(cin, employees[count].name); должен помочь.

person Ken Wayne VanderLinde    schedule 03.02.2012

Вы можете использовать функцию getline, которая принимает входной поток в качестве первого аргумента. Его прототип выглядит так: basic_istream<...>& getline(basic_istream<...>& _Istr, basic_string<...>& _Str)

Но тогда вам нужно подумать о том, что вы имеете дело с std::string, и проанализировать его как тип, который вам действительно нужен (char*, int, что угодно):

#include <iostream>
#include <sstream>
#include <string>
using namespace std;
//...

for (int count=0; count < numberOfEmployees; count++)
{
    string name, title, sSSnum, sSalary, s;

    cout << "Name: ";
    getline(cin, name); 
    employees[count].name = name.c_str();

    cout << "Title: ";
    getline(cin, title);
    employees[count].title = name.c_str();

    cout << "SSNum: ";
    getline(cin, sSSnum);
    istringstream is(sSSnum);
    is >> employees[count].SSNum;

    cout << "Salary: ";
    getline(cin, sSalary);
    is.clear();
    is.str(sSalary);
    is >> employees[count].Salary;
    //...
}
person LihO    schedule 03.02.2012

istream& getline (char* s, размер потока n); istream& getline (char* s, размер потока n, разделитель символов ); Получить строку из потока

Извлекает символы из входной последовательности и сохраняет их как c-строку в массиве, начиная с s.

Символы извлекаются до тех пор, пока не будет извлечено (n - 1) символов или пока не будет найден символ-разделитель (который является разделителем, если указан этот параметр, или '\n' в противном случае). Извлечение также останавливается, если во входной последовательности достигнут конец файла или если во время операции ввода возникает ошибка.

Если разделитель найден, то он извлекается и отбрасывается, т.е. не сохраняется и после него начинается следующая операция ввода. Если вы не хотите, чтобы этот символ извлекался, вы можете вместо этого использовать член.

Конечный нулевой символ, сигнализирующий об окончании c-строки, автоматически добавляется к s после извлечения данных.

Количество символов, прочитанных этой функцией, можно получить, вызвав функцию-член gcount.

Глобальная функция с таким же именем существует в заголовке. Эта глобальная функция обеспечивает аналогичное поведение, но со стандартными строковыми объектами C++ вместо c-строк: см. getline (строка).

Параметры s Указатель на массив символов, где строка хранится как c-строка. n Максимальное количество сохраняемых символов (включая завершающий нулевой символ). Это целочисленное значение типа streamsize. Если функция прекращает чтение из-за достижения этого размера, устанавливается внутренний флаг failbit. delim Символ-разделитель. Операция извлечения последовательных символов останавливается при чтении этого символа. Этот параметр является необязательным, если он не указан, функция считает '\n' (символ новой строки) символом-разделителем.

Возвращаемое значение Функция возвращает *this.

ПРИМЕР MSDN:

// istream getline
#include <iostream>
using namespace std;

int main () {
  char name[256], title[256];

  cout << "Enter your name: ";
  cin.getline (name,256);

  cout << "Enter your favourite movie: ";
  cin.getline (title,256);

  cout << name << "'s favourite movie is " << title;

  return 0;
}

И для вашей ситуации с объектом структуры вы должны использовать objectname.datamember для доступа к данным объектов:

for (int count=0; count < numberOfEmployees; count++)
    {
        cout << "Name: ";
       cin.getline (employees[count].name,100); 

        cout << "Title: ";
        cin.getline (employees[count].title,100); 

        cout << "SSNum: ";
        cin.getline (employees[count].SSNum); 

        cout << "Salary: ";
        cin.getline( employees[count].Salary);

        cout << "Withholding Exemptions: ";
       cin.getline (employees[count].Withholding_Exemptions);
    }
person Rohit Vipin Mathews    schedule 03.02.2012
comment
Когда я делаю это для имени и названия, программа запускается, но одновременно отображает имя и название и записывает только название. Что не так? - person Gvegas222; 03.02.2012
comment
проблема с промывкой. Если вы действительно хотите знать, прочитайте ответ!! - person Rohit Vipin Mathews; 03.02.2012
comment
лучше используйте cin, чтобы вы могли не думать о получении ввода в необходимых форматах - person Rohit Vipin Mathews; 03.02.2012