Using Getline() in C++

Reading two line continuously using getline function in c++

std::istream::getline is being overloaded with data.

According to cppreference,

Behaves as UnformattedInputFunction. After constructing and checking the sentry object, extracts characters from *this and stores them in successive locations of the array whose first element is pointed to by s, until any of the following occurs (tested in the order shown):

  • end of file condition occurs in the input sequence (in which case setstate(eofbit) is executed)
  • the next available character c is the delimiter, as determined by Traits::eq(c, delim). The delimiter is extracted (unlike basic_istream::get()) and counted towards gcount(), but is not stored.
  • count-1 characters have been extracted (in which case setstate(failbit) is executed).

Emphasis mine.

cin.getline(line1,7);
// ^ This is count

can read only 6 characters with the 7th reserved for the null terminator. "oranges" is seven characters, and this places cin in a non-readable error state that must be cleared before reading can be continued. Reading of the second line

cin.getline(line2,7);

instantly fails and no data is read.

The obvious solution is

cin.getline(line1, sizeof(line1));

to take advantage of the whole array. But...

Any IO transaction should be tested for success, so

if (cin.getline(line1, sizeof(line1)))
{
// continue gathering
}
else
{
// handle error
}

is a better option.

Better still would be to use std::getline and std::string to almost eliminate the size constraints.

Is using getline() in a while loop bad practice?

There is no problem calling getline in a loop as you do. As a matter of fact, this exactly the intended use case and used in the example in the man page.

realloc is only called if the array allocated so far is too short for the current line, thus it will be called very little and you can even lower the number of calls by allocating an initial array and set its length in the len variable.

Since getline returns the number of characters read, you could write the line using fwrite instead of fputs. Note however that the behavior is subtly different: fwrite will output lines read from the file containing embedded null bytes, whereas fputs would stop on the first null byte. This is usually not an issue as text files usually do not contain null bytes.

Here is a modified version:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
fprintf(stderr, "Unable to open file %s: %s\n",
"file.txt", strerror(errno));
return 1;
}
char *buffer = NULL;
size_t len = 0;
ssize_t characters;

#if 1
// optional code to show how to start with a preallocated buffer
if ((buffer = malloc(256)) != NULL)
len = 256;
#endif

while ((characters = getline(&buffer, &len, fp)) != -1) {
fwrite(buffer, 1, characters, stdout);
}
fclose(fp);
free(buffer);
return 0;
}

Using getline along with dynamic storage

Since the goal is "print the contents in reverse order of the user's input, last line first", the program must store all the lines. The getline() function typically allocates quite a large space for each line (128 bytes by default on my Mac, and growing if input lines are longer than that), so it is usually best to have a buffer managed by getline() that can grow if need be, and to copy the actual input strings somewhere else with the requisite length. I use strdup() to copy the line.

/* Read file and print the lines in reverse order */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
char **ptrs = 0;
size_t numptrs = 0;
size_t count = 0;
char *buffer = 0;
size_t buflen = 0;

while (getline(&buffer, &buflen, stdin) != -1)
{
if (count == numptrs)
{
size_t newnum = (numptrs + 2) * 2;
void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));
if (newptrs == 0)
{
fprintf(stderr, "Out of memory (%zu bytes requested)\n", newnum * sizeof(*ptrs));
exit(1);
}
ptrs = newptrs;
numptrs = newnum;
}
ptrs[count++] = strdup(buffer);
}

free(buffer);

/* Print lines in reverse order */
for (size_t i = count; i > 0; i--)
fputs(ptrs[i-1], stdout);

/* Free allocated memory */
for (size_t i = 0; i < count; i++)
free(ptrs[i]);
free(ptrs);

return 0;
}

It's easy to argue that the code should check that strdup() succeeds and take appropriate action if it does not. The code freeing the allocated memory should be in a function — that would make clean up after an error easier, too. The code could be revised into a function that could be used to process files listed as command-line arguments instead of standard input.

Given this text as standard input:

Because it messes up the order in which people normally read text.
> Why is top-posting such a bad thing?
>> Top-posting.
>>> What is the most annoying thing in e-mail?

the program produces the output:

>>> What is the most annoying thing in e-mail?
>> Top-posting.
> Why is top-posting such a bad thing?
Because it messes up the order in which people normally read text.

getline() in C creating an infinite loop and skipping first word?

but it's just creating an infinite loop.

This line of your code: while ((read = getline(&line, &len, list)) != 1)

Unless a line contains 1 character (just a newline), it will be an infinite loop. The POSIX getline() function will return -1 (not EOF, even though EOF is usually -1) when the file is completely read. So change that line to:

while ((read = getline(&line, &len, list)) != -1)

But, I don't see you using the value of read inside that loop, so this would be better:

  • Fix 1: while (getline(&line, &len, list) != -1)

And inside that loop, I see: printf("%d. %s", item, line);

You might find very old implementations of getline() that don't include the newline, in which case, if you want your output in separate lines, you need to put a \n:

  • Fix 2: printf("%d. %s\n", item, line);

However, if you use a more modern implementation, it will preserve the newline in accordance with the POSIX specification.

Also, if the very last 'line' in the file is not terminated with a newline, you might still want to add one. In that case, you could keep the read length and use that to detect whether there is a newline at the end of the line.

Also it seems to be skipping the first word for some reason

Because of int letterCount = fscanf(list,"%s",chars);

That fscanf reads the first word of your file. Now the file pointer is at that position (end of the first word) and further reading of the file will happen from that place.

So, reposition the file pointer to the beginning of the file after reading the first word from the file:

  • Fix 3:

    int letterCount = fscanf(list,"%s",chars);
    fseek(list, 0, SEEK_SET); // <-- this will reposition the file pointer as required

Using getline(cin, s) after cin

cout << "Enter the number: ";
int number;
if (cin >> number)
{
// throw away the rest of the line
char c;
while (cin.get(c) && c != '\n')
if (!std::isspace(c))
{
std::cerr << "ERROR unexpected character '" << c << "' found\n";
exit(EXIT_FAILURE);
}
cout << "Enter names: ";
string name;
// keep getting lines until EOF (or "bad" e.g. error reading redirected file)...
while (getline(cin, name))
...use name...
}
else
{
std::cerr << "ERROR reading number\n";
exit(EXIT_FAILURE);
}

In the code above, this bit...

    char c;
while (cin.get(c) && c != '\n')
if (!std::isspace(c))
{
std::cerr << "ERROR unexpected character '" << c << "' found\n";
exit(EXIT_FAILURE);
}

...checks the rest of the input line after the number contains only whitespace.

Why not just use ignore?

That's pretty verbose, so using ignore on the stream after >> x is an oft-recommended alternative way to discard content through to the next newline, but it risks throwing away non-whitespace content and in doing so, overlooking corrupt data in the file. You may or may not care, depending on whether the file's content's trusted, how important it is to avoid processing corrupt data etc..

So when would you use clear and ignore?

So, std::cin.clear() (and std::cin.ignore()) isn't necessary for this, but is useful for removing error state. For example, if you want to give the user many chances to enter a valid number.

int x;
while (std::cout << "Enter a number: " &&
!(std::cin >> x))
{
if (std::cin.eof())
{
std::cerr << "ERROR unexpected EOF\n";
exit(EXIT_FAILURE);
}

std::cin.clear(); // clear bad/fail/eof flags

// have to ignore non-numeric character that caused cin >> x to
// fail or there's no chance of it working next time; for "cin" it's
// common to remove the entire suspect line and re-prompt the user for
// input.
std::cin.ignore(std::numeric_limits<std::streamsize>::max());
}

Can't it be simpler with skipws or similar?

Another simple but half-baked alternative to ignore for your original requirement is using std::skipws to skip any amount of whitespace before reading lines...

if (std::cin >> number >> std::skipws)
{
while (getline(std::cin, name))
...

...but if it gets input like "1E6" (e.g. some scientist trying to input 1,000,000 but C++ only supports that notation for floating point numbers) won't accept that, you'd end up with number set to 1, and E6 read as the first value of name. Separately, if you had a valid number followed by one or more blank lines, those lines would be silently ignored.

Taking user input with getline() and comparing to another string in a while loop

Replace

while(buffer != "exit");

with

while(strcmpi(buffer, "exit"));

In C, you can't check logical equality of string with == or != operators. You need to use functions like 'strcmp' or strcmpi.

Using getline() in C++

If you're using getline() after cin >> something, you need to flush the newline character out of the buffer in between. You can do it by using cin.ignore().

It would be something like this:

string messageVar;
cout << "Type your message: ";
cin.ignore();
getline(cin, messageVar);

This happens because the >> operator leaves a newline \n character in the input buffer. This may become a problem when you do unformatted input, like getline(), which reads input until a newline character is found. This happening, it will stop reading immediately, because of that \n that was left hanging there in your previous operation.



Related Topics



Leave a reply



Submit