Friday, February 1, 2008, 04:25 AM -
Programming
While programming in C++, very often, probably most often, most people prefer to use
scanf() and
printf() for satisfying their program's hunger and excretion needs, rather than using C++
std::cin and
std::cout .In my experience I have seen most C++ programmers having very little control over C++ IO routines. They seem to go back to C style of programming while dealing with IO (be it stdout/stdin or file read/writes).
FILE* fp appears more often than not that
std::fstream.
After reading through some articles how using a mix of
std::cout and
printf() could produce unpredictable behavior, I am making conscious effort to only using
std::cout and
std::cin in all my C++ programs. I will agree with all those people that using printf() is easier when you are writing small programs --- writing std::endl is always a little more painful then just writing "\n" or getting an integer formatted in hex is just saying "%0x" in C whereas in C++ its like saying "<< std::base::hex <<". Formatting is much easier in C than in C++.
To illustrate,
printf(" %d : %s | The file, %s has %d lines and is located
at offset Ox%X.\n", __LINE__ , __FUNCTION__, filename,
lines , offset);is easier to write and foretell the output than
std::cout << " " << __LINE__ << " : " << __FUNCTION__
<< " | The file, " << filename << " has " << lines
<< " lines and is located at offset Ox" << std::hex
<< offset << std::endl;
But when you need to do complex stuff, C++ can be quite effective and more immune to program crash and more safe than C, but that's an altogether different discussion -- I mention this not to sound like I am vehemently rejecting its beauty.
In today's diary, I am writing about reading characters from stdin and some common problems/issues that always seem to make me feel like I am reinventing the wheel.
When you do a
std::cin >> var; , it does a pretty decent job while reading integers, floats, characters or words.
What if you want to read a line from stdin. Lets say I was to read my name "Shuva Brata Deb" into a string. Just like scanf, std::cin by default ignores all white spaces and tabs. So just saying
std::cin >> str; just reads "Shuva".
I did some search in my quest for reading lines and interestingly(as expected) there are more than one way of getting this done. Here are my notes:
1.Using the getline function.#include <iostream>
#include <sstream>
int main() {
std::string str;
std::cout << "Name:";
getline(std::cin, str);
std::cout << str;
std::cout << std::endl << "Company:";
getline(std::cin, str);
std::cout << str;
}
So instead of using
std::cin >> str; you use the
getline global function to read the string. It is recommended that you don’t mix the two styles of reading input. The reason for this recommendation is that when you say
std::cin >> str; it leaves the end-of-line character in the input buffer. At this stage if you call
getline(std::cin, str), it only reads the left-over EOL char. It gives a feeling that the
getline(std::cin, str) did not do anything.
To illustrate :
#include <iostream>
#include <sstream>
int main() {
std::string str;
std::cout << "Name:";
std::cin >> str;
std::cout << str;
std::cout << std::endl << "Company:";
getline(std::cin, str);
std::cout << str << std::endl;
}
Would give different outputs when you enter two words in one line or one word in the first line and press enter.
Here is the two outputs, which can be a bit confusing:
C:\readline.exe
Name:Shuva Brata Deb
Shuva
Company: Brata Deb
Press any key to continue . . .
<Here the company was mis-read, but this was expected>
C:\readline.exe
Name:Shuva
Shuva
Company:
Press any key to continue . . .
<Here you did not get a chance to enter the company>
The correct solution is of course to use only getline(), but the point (as we see in the second output) is that we should try not to use them interchangeably.
But we know such recommendation have no value, because sometimes we do need to break the recommendation. The question is how do you make a
readline(std::cin, str) after a
std::cin >> str; behave properly.
First solution: (not very good solution):Call readline(std::cin, str); twice. The first call eats the left over end-of-line
#include <iostream>
#include <sstream>
int main() {
std::string str;
std::cout << "Name:";
std::cin >> str;
std::cout << str;
std::cout << std::endl << "Company:";
getline(std::cin, str);
getline(std::cin, str);
std::cout << str << std::endl;
}
Second solution: Call std::cin.get() to read the left-over end of line.#include <iostream>
#include <sstream>
int main() {
std::string str;
std::cout << "Name:";
std::cin >> str;
std::cout << str;
std::cout << std::endl << "Company:";
char eol(0);
std::cin.get(eol);
getline(std::cin, str);
std::cout << str << std::endl;
}
Third solution: Check for the EOL char and ignore.#include <iostream>
#include <sstream>
int main() {
std::string str;
std::cout << "Name:";
std::cin >> str;
std::cout << str;
std::cout << std::endl << "Company:";
char eol(0);
if (std::cin.peek()=='\n') std::cin.ignore(1,'\n');
getline(std::cin, str);
std::cout << str << std::endl;
}Using getline() is more robust that std::cin. You can get getline to work with file streams too, you can make getline() to stop reading after a specific character, which by default is ‘\n’. You end up with have consistency in your code base if you stick to one, and one which is more robust and solves more problems.
Happy Getlining.//