Mixing Ifstream Getline and >>

Mixing ifstream getline and

First of all, start using std::strings and std::getline. Secondly, the reason for the trashing is that there's still a newline in the buffer. If you expect the user to be entering values one line at a time, you should use istream::ignore to skip everything up to and including the next newline character. For example:

std::string s;
float f;
std::cin >> f;
std::cin.ignore(big_num, '\n');
std::getline(std::cin, s);

What big_num should be depends on your expectations about the input. If you don't mind writing a lot and want to be on the safe side, use std::numeric_limits<std::streamsize>::max(). If you do mind writing a lot, make a constant of the suitable type that you use everywhere.

For example, the above code will parse the following so that f = 5.0f, s = Hello!.

5
Hello!

However, it will parse the following exactly the same way:

5 Hi!
Hello!

If you want to preserve the Hi!, you shouldn't ignore things, and instead define a proper grammar that you'll use to parse the document.

How to avoid conflict between cin.operator and getline?

The problem with mixing formatted input like std::cin >> n with unformatted input like std::getline(std::cin, film) is that formatted input stops when the format is filled and unformatted input doesn't skip any space characters: the issue you are observing is that reading of n left a newline character which considered the end of the first string. The best way to solve this problem is to skip all leading whitespace using the std::ws manipulator. In addition, you shall always verify that your input was successful. That is, you code would become something like this:

#include <iostream>
int main() {
int n;
if (std::cin >> n) {
std::string film;
std::cin >> std::ws; // not really an input, hence uncheckedd
for (int i(0); i != n && std::getline(std::cin, film); ++i) {
std::cout << '\'' << film << "'\n";
}
}
else {
std::cout << "ERROR: failed to read the number of films\n";
}
}

If you really need to have leading whitespaces in the name of the first film you'd need to be more careful with ignoring whitespace and stop at the first newline. That is instead of std::cin >> std::ws you'd use

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

This funny use of std::numeric_limits (which is declared in the header <limits>) makes sure that an arbitrary number of spaces could be skipped. You could also use some fixed number, say 10, but hen skipping of spaces would stop after this number and you'd still potentially empty file. Also, if you actually entered the first film name on the same line as the number of files it would be skipped. As a result I think std::ws is much better to use.

Use getline and when read file C++

After reading the last score, the line break is still sitting on the input buffer. You need to skip that. The ignore function is useful for that.

getline(file, player[i].name);
for (int k = 0; k < 5; ++k)
file >> player[i].grade[k];
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

Check for errors as appropriate. The >> operator, getline, and ignore all return the stream reference, which you can check for success or failure.


There's no need for that j loop since each iteration does a completely different thing. Just write the j=0 case immediately followed by the j=1 case, and then get rid of the loop, like my code above. (And note that j will never equal 2 inside the loop, so your condition was wrong anyway.)

Why does std::getline() skip input after a formatted extraction?

Why does this happen?

This has little to do with the input you provided yourself but rather with the default behavior std::getline() has. When you provided your input for the age (std::cin >> age), you not only submitted the following characters, but also an implicit newline was appended to the stream when you typed Enter:

"10\n"

A newline is always appended to your input when you select Enter or Return when submitting from a terminal. It is also used in files for moving toward the next line. The newline is left in the buffer after the extraction into age until the next I/O operation where it is either discarded or read. When the flow of control reaches std::getline(), it will see "\nMr. Whiskers" and the newline at the beginning will be discarded, but the input operation will stop immediately. The reason this happens is because the job of std::getline() is to attempt to read characters and stop when it finds a newline. So the rest of your input is left in the buffer unread.

Solution

cin.ignore()

To fix this, one option is to skip over the newline before doing std::getline(). You can do this by calling std::cin.ignore() after the first input operation. It will discard the next character (the newline character) so that it is no longer in the way.

std::cin >> age;
std::cin.ignore();
std::getline(std::cin, name);

assert(std::cin);
// Success!

std::ws

Another way to discard the whitespace is to use the std::ws function which is a manipulator designed to extract and discard leading whitespace from the beginning of an input stream:

std::cin >> age;
std::getline(std::cin >> std::ws, name);

assert(std::cin);
// Success!

The std::cin >> std::ws expression is executed before the std::getline() call (and after the std::cin >> age call) so that the newline character is removed.

The difference is that ignore() discards only 1 character (or N characters when given a parameter), and std::ws continues to ignore whitespace until it finds a non-whitespace character. So if you don't know how much whitespace will precede the next token you should consider using this.

Match the operations

When you run into an issue like this it's usually because you're combining formatted input operations with unformatted input operations. A formatted input operation is when you take input and format it for a certain type. That's what operator>>() is for. Unformatted input operations are anything other than that, like std::getline(), std::cin.read(), std::cin.get(), etc. Those functions don't care about the format of the input and only process raw text.

If you stick to using a single type of formatting then you can avoid this annoying issue:

// Unformatted I/O
std::string age, name;
std::getline(std::cin, age);
std::getline(std::cin, name);

or

// Formatted I/O
int age;
std::string firstName, lastName;
std::cin >> age >> firstName >> lastName;

If you choose to read everything as strings using the unformatted operations you can convert them into the appropriate types afterwards.

How do I use getline to read from a file and then tokenize it using strtok?

First of all, the line

if (argc = 2)

is probably not doing what you intend. You should probably write this instead:

if (argc == 2)

The function std::istream::getline requires as a first parameter a char *, which is the address of a memory buffer to write to. However, you are passing it the 2D array words, which does not make sense.

You could theoretically pass it words[0], which has space for 16 characters. Passing words[0] will decay to &words[0][0], which is of the required type char *. However, the size of 16 characters will probably not be sufficient. Also, it does not make sense to write the whole line into words, as this 2D array seems to be intended to store the result of strtok.

Therefore, I recommend that you introduce an additional array that is supposed to store the entire line:

char line[200];
(...)
while( inputfile.getline( line, sizeof line ) )

Also, the line

token = strtok(words[100][16], " ");

does not make sense, as you are accessing the array words out of bounds. Also, it does not make sense to pass a 2D array to std::strtok, either.

Another issue is that you should call std::strtok several times, once for every token. The first parameter of std::strtok should only be non-NULL on the first invocation. It should be NULL on all subsequent calls, unless you want to start tokenizing a different string.

After copying all tokens to words, you can then print them in a loop:

#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;

int main(int argc, char **argv)
{
char line[200];
char words[100][16];
int counter = 0;

ifstream inputfile;

inputfile.open(argv[1]);

while( inputfile.getline( line, sizeof line) )
{
char *token;

token = strtok( line, " ");

while ( token != nullptr )
{
strcpy( words[counter++], token );
token = strtok( nullptr, " " );
}
}

//print all found tokens
for ( int i = 0; i < counter; i++ )
{
cout << words[i] << '\n';
}
}

For the input

This is the first line.
This is the second line.

the program has the following output:

This
is
the
first
line.
This
is
the
second
line.

As you can see, the strings were correctly tokenized.

However, note that you will be writing to the array words out of bounds if

  • any of the tokens has a size larger than 16 characters, or
  • the total number of tokens is higher than 100.

To prevent this from happening, you could add additional checks and abort the program if such a condition is detected. An alternative would be to use a std::vector of std::string instead of a fixed-size array of C-style strings. That solution would be more flexible and would not have the problems mentioned above.

c++ getline() isn't waiting for input from console when called multiple times

The problem is you are mixing calls to getline() with the use of the operator >>.

Remember that operator >> ignored leading white space so will correctly continue across lines boundaries. But stops reading after the input has successfully been retrieved and thus will not swallow trailing '\n' characters. Thus if you use a getline() after a >> you usually get the wrong thing unless you are careful (to first remove the '\n' character that was not read).

The trick is to not use both types of input. Pick the appropriate one and stick to it.

If it is all numbers (or objects that play nice with operator >>) then just use operator >> (Note string is the only fundamental type that is not symmetric with input/output (ie does not play nicely)).

If the input contains strings or a combination of stuff that will require getline() then only use getline() and parse the number out of the string.

std::getline(std::cin, line);
std::stringstream linestream(line);

int value;
linestream >> value;

// Or if you have boost:
std::getline(std::cin, line);
int value = boost::lexical_cast<int>(line);

Reading getline from cin into a stringstream (C++)

You are almost there, the error is most probably1 caused because you are trying to call getline with second parameter stringstream, just make a slight modification and store the data within the std::cin in a string first and then used it to initialize a stringstream, from which you can extract the input:

// read input
string input;
getline(cin, input);

// initialize string stream
stringstream ss(input);

// extract input
string name;
string course;
string grade;

ss >> name >> course >> grade;

1. Assuming you have included:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;


Related Topics



Leave a reply



Submit