This question already has answers here:
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 9 years ago.
I'm creating a program that stores basic data. After inputing data for the first 'person', the program simultaneously loops to the second instance of i without accepting any input. This is hard to explain but I hope someone can help me out. I have a feeling that getline and cin.ignore are causing these problems. I only came across them on this website.
struct info {
string name;
string address;
int phone;
};
int main(){
int input;
cout<<"How many people do you want on the list" <<endl;
cin>>input;
info arr[input];
for(int i=0;i<input;i++){
cout<<"Enter name for person " <<i+1 <<": ";
getline(cin,arr[i].name);
cin.ignore(1000,'\n');
cout<<"Enter the address of " <<arr[i].name <<endl;
getline(cin,arr[i].address);
cin.ignore(1000, '\n');
cout<<"Enter phone number of " <<arr[i].name <<endl;
cin>>arr[i].phone;
cout<<endl;
}
}
Mixing formatted input with unformatted input is always asking for trouble. When you extract input into the stream, a newline is always appended. The newline character is the delimiter for std::getline(), meaning whilst extraction is being performed, if std::getline() finds a newline, it discards that character and stops input.
This is the problem you're running into. After the last formatted extraction cin >> input, the newline is left in the input stream. std::getline() (unlike formatted input functions) do not discard leading whitespace, so it is prone to the disadvantage I explained above.
To solve this issue, you need to discard leading whitespace manually, so the the non-whitespace input is ready to be extracted by the time std::getline() is invoked:
std::getline(std::cin >> std::ws, arr[i].name);
// ^^^^^^^^^^^^^^^^^^^
std::ws is manipulator which extracts and discards leading whitespace. This should be satisfactory for your purposes.
The following are suggestions that I advise you take. They lead to a substantially cleaner, clearer code structure. And it also reduces a lot of headaches:
1. Your class should have its own inserter/extractor.
You should define your own inserter/extractor to encapsulate I/O semantics for your class. It facilitates a lot of the trouble of manual extraction. Inserters/extractors are typically implemented as friends of the target class so that it may access private data members. Your class in particular has no private data members, but this is still an idiomatic technique nontheless:
struct info
{
string name;
string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
Inserters/extractors are global functions because they are being defined in terms of (not only) the target class, but also the stream object itself.
2. Use std::vector<T> rather than raw C-style arrays
The following is wrong:
cin >> input;
info arr[input];
This code instantiates an array at compile-time with a size known only at runtime. The possibility of your compiler accepting this code fully depends on how standard-conformant it is. GCC has a non-standard extension which welcomes code like that, but it is non-standard even so and shouldn't be used.
If you need an array of objects whose size is known at runtime, use a vector of those objects. A vector class is already provided for you through the Standard library:
std::cin >> input;
std::vector<info> arr(input);
In fact, the size is superfluous since the vector will grow dynamically as more and more elements are created. You can simply default-construct the vector (unless you have a good reason to specify the size, in which case that many info objects will be default-constructed in the internal buffer of the vector).
std::vector<info> arr;
This is mostly it. Here is an example of your program that applies these two suggestions:
#include <iostream>
#include <vector>
struct info
{
std::string name;
std::string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
std::ostream& operator<<(std::ostream& os, const info& obj)
{
return os << "Person's name is: " << obj.name << std::endl
<< obj.name << "'s address is: " << obj.address << std::endl
<< obj.name << "'s phone number is: " << obj.phone << std::endl;
}
std::istream& operator>>(std::istream& is, info& obj)
{
std::cout << "Enter name for person " << ": ";
std::getline(is >> std::ws, obj.name);
std::cout << "Enter the address of " << obj.name << std::endl;
std::getline(is >> std::ws, obj.address);
std::cout << "Enter phone number of " << obj.name << std::endl;
std::cin >> obj.phone;
std::cout << endl;
return is;
}
int main()
{
int input;
std::cout << "How many people do you want on the list" << std::endl;
std::cin >> input; // just as a formality, but really the user can enter
// as much as they want.
// The value of input will be ignored.
std::vector<info> arr;
info obj;
while (std::cin >> obj)
{
arr.push_back(obj);
}
for (const auto& item : arr)
std::cout << item << std::endl;
}
If you have any questions, please leave them as a comment and I will try to help you as best as possible.
Related
This question already has an answer here:
C++ - overloading operator >> for my string class
(1 answer)
Closed 9 months ago.
I am trying to read user entered data from the stream and then store it in a custom String class.
To my best knowledge, std::getline() can route data only to std::string , that is why I need to come up with something else, as my project is not allowed to use std::string class.
My code looks like this:
String street();
std::cout << "Street: "; std::cin >> std::noskipws;
char c='\0';
while(c!='\n'){
std::cin >> c;
street=street+c;
}std::cin >> std::skipws;
int bal=0;
std::cout << "Balance: "; std::cin >> bal;
To my best knowledge, std::getline() can route data only to std::string , that is why I need to come up with something else, as my project is not allowed to use std::string class.
Note that std::getline and std::istream::getline are two separate functions. The former will work with std::string while the latter will work with C-style strings (i.e. sequences of characters that are terminated by a null character).
Therefore, if you are not allowed to use std::string, then you can still use std::istream::getline, for example like this:
char line[200];
String street;
std::cout << "Street: ";
if ( std::cin.getline( line, sizeof line ) )
{
//the array "line" now contains the input, and can be assigned
//to the custom String class
street = line;
}
else
{
//handle the error
}
This code assumes that your custom class String has defined the copy assignment operator for C-style strings.
If it is possible that the lines will be larger than a fixed number of characters and you want to support such lines, then you could also call std::istream::getline in a loop:
char line[200];
String street;
std::cout << "Street: ";
for (;;)
{
std::cin.getline( line, sizeof line );
street += line;
if ( std::cin.bad() )
{
//TODO: handle error and break loop, for example by
//throwing an exception
}
if ( !std::cin.fail() || std::cin.eof() )
break;
std::cin.clear();
}
This code assumes that operator += is defined for class String.
This loop will continue forever until
getline succeeds (i.e. it is able to extract (but not store) the newline character), or
end-of-file is reached (eofbit is set), or
an error occurs (badbit is set).
You can use the C function "getchar()" to read a single character from standard input. This link describes it: https://www.ibm.com/docs/en/i/7.3?topic=functions-getc-getchar-read-character.
Here is my code:
String street=();
std::cout << "Street: ";
char c='\0';
while(c!='\n'){
c = getchar();
street=street+c;
}
int bal=0;
std::cout << "Balance: "; std::cin >> bal;
cout << street << endl;
I hope this will help you, and I recommend you make an independent function that will read line from standard input and whose return type will be "String". You can declare it as:
String readLine();
And I also recommend you to pay attention to that while loop because string that is obtained from that loop will have character '\n' at the end of it.
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
string name, choice;
int age;
cout << "Enter a name: ";
//Getline will allow to take an entire line as an input and not a char
getline(cin, name);
cout << "Enter choice of meal: ";
getline(cin, choice);
cout << "Enter age: ";
**getline(cin, age);**
cout << "My name is " << name << endl;
return 0;
}
This is my code
The problem is in the bold line can anyone please tell me why the error is coming and the solution for it?
The answer why you code doesn't work is that getline() online allows you to read strings but not integers. Interestingly, there are no facilitien provided for reading other types in the C++ IOStreams library. This is so interesting, because line-based and delimiter-based stream processing, and in particular mixing them, is always a source for problems. Being able to use the approach you tried would be much easier and safer, I would say.
There's a simple solution (untested):
template<typename value_type>
istream&
getline(std::istream& in, value_type& val)
{
std::string line;
if (!getline(in, line)) {
return in;
}
std::istringstream iss(line);
if (!(iss >> val)) {
in.setf(std::ios::failbit);
}
return in;
}
Basically, it reads a line and then converts it to the according target type in two steps.
When you get errors like this, it's time to look up the signature of the function you use. E.g. look at this documentation std::getline. All of the overloads only accept a (template version of) std::basic_string&, there is no version accepting an int. So you need to look for another way of going from a string input an to an integer value. In this case you might want to check the documentation of the <string> header.
I'm having trouble coding and conceptualizing this project I was assigned. I've looked around for answers to this issue but had little to no luck, maybe it's really obvious. I'm supposed to prompt the user to a filename, the file is assume to have the following format:
Animal:
Name: [value]
Noise: [value]
Legs: [value]
(with no spaces in between)
It should be able to read as many "animal objects" as there are in the file and store them in an animal object class that has 3 parameters (name, noise, legs).
My issue is mostly during the reading in of the file, I can't figure out a good method for reading the file AND storing the information. Here is the code I currently have. Any help with the code I currently have and ideas for storing the values. Sorry if I explained anything poorly, please ask to clarify if I did, thank you in advance.
cout << "Enter the file name: ";
string fileName;
getline(cin, fileName);
cout << endl;
try
{
ifstream animalFile(fileName);
if (!animalFile.good()) // if it's no good, let the user know and let the loop continue to try again
{
cout << "There was a problem with the file " << fileName << endl << endl;
continue;
}
string line;
while (animalFile >> line) // To get you all the lines.
{
getline(animalFile, line); // Saves the line in STRING.
cout << line << endl; // Prints our STRING.
}
}
catch (...)
{
cout << "There was a problem with the file " << fileName << endl << endl;
}
If you're really binded with this file format, consider doing the following to read the data and store it:
#1. Define a class Animal to represent an animal:
struct Animal
{
std::string name;
int legs;
int noise;
}
#2. Define an istream& operator >> (istream&, Animal&) to read one object of this type and check for the correctness of the input.
std::istream& operator >> (std::istream& lhs, Animal& rhs)
{
std::string buf;
lhs >> buf >> buf >> rhs.name >> buf >> rhs.noise >> buf >> rhs.legs;
}
#3. Use std::copy and std::istream_iterator to read all the values from the file to std::vector:
std::istream_iterator<Animal> eos;
std::istream_iterator<Animal> bos(animalFile);
std::vector<Animal> zoo;
std::copy(bos, eos, std::back_inserter(zoo));
This code has no checking for input errors, which may easily be added into istream& operator >> (istream&, Animal&).
I want to read in from txt file into structure using fstream.
I save the data to the file in the way shown below:
To read the data i tried some cheeky stuff with getlines or tabsin<
struct tab{
int type,use;
string name, brand;
};
tab tabs[500];
ofstream tabsout;
tabsout.open("tab.txt", ios::out);
for (int i = 0; i < 500; i++){
if (tabs[i].use==1){
tabsout << tabs[i].type << " " << tabs[i].name << " " << tabs[i].brand << "\n";
}
}
tabsout.close();
//input part that fails me :(
int i=0;
ifstream tabsin;
tabsin.open("tab.txt", ios::in);
if (tabsin.is_open()){
while(tabsin.eof() == false)
{
tabsin >> tabs[i].type>>tabs[i].name>>tabs[i].brand;
i++
}
tabsin.close();
You usually want to overload operator>> and operator<< for the class/struct, and put the reading/writing code there:
struct tab{
int type,use;
string name, brand;
friend std::istream &operator>>(std::istream &is, tab &t) {
return is >> t.type >> t.name >> t.brand;
}
friend std::ostream &operator<<(std::ostream &os, tab const &t) {
return os << t.type << " " << t.name << " " << t.brand;
}
};
Then you can read in a file of objects like:
std::ifstream tabsin("tab.txt");
std::vector<tab> tabs{std::istream_iterator<tab>(tabsin),
std::istream_iterator<tab>()};
....and write out the objects like:
for (auto const &t : tabs)
tabsout << t << "\n";
Note that (like any sane C++ programmer) I've used a vector instead of an array, to (among other things) allow storing an arbitrary number of items, and automatically track how many are actually being stored.
For starters, do not use .eof() to control your loop: it doesn't work. Instead, use the stream's state after reading:
int type;
std::string name, brand;
while (in >> type >> name >> brand) {
tabs.push_back(tab(type, name, brand));
}
If your name or brand contain spaces, the above won't work and you will need to write a format where you can know when to stop abd read correspondingly, e.g., using std::getline().
You might also consider wrapping the logic to read or write an object by suitable operators.
istream& getline (istream& is, string& str, char delim);
Take a look at the third parameter, you can use std::getline to parse your line. But that is definitely not the best way to serialize objects. Instead of using a text file, you should use a byte stream.
This question already has answers here:
Need help with getline() [duplicate]
(7 answers)
Closed 7 years ago.
This is probably a very simple problem but forgive me as I am new.
Here is my code:
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main ()
{
string name;
int i;
string mystr;
float price = 0;
cout << "Hello World!" << endl;
cout << "What is your name? ";
cin >> name;
cout << "Hello " << name << endl;
cout << "How old are you? ";
cin >> i;
cout << "Wow " << i << endl;
cout << "How much is that jacket? ";
getline (cin,mystr);
stringstream(mystr) >> price;
cout << price << endl;
system("pause");
return 0;
}
The problem is that when asked how much is that jacket? getline does not ask the user for input and just inputs the initial value of "0". Why is this?
You have to be careful when mixing operator>> with getline. The problem is, when you use operator>>, the user enters their data, then presses the enter key, which puts a newline character into the input buffer. Since operator>> is whitespace delimited, the newline character is not put into the variable, and it stays in the input buffer. Then, when you call getline, a newline character is the only thing it's looking for. Since that's the first thing in the buffer, it finds what it's looking for right away, and never needs to prompt the user.
Fix:
If you're going to call getline after you use operator>>, call ignore in between, or do something else to get rid of that newline character, perhaps a dummy call to getline.
Another option, and this is along the lines of what Martin was talking about, is to not use operator>> at all, and only use getline, then convert your strings to whatever datatype you need. This has a side effect of making your code more safe and robust. I would first write a function like this:
int getInt(std::istream & is)
{
std::string input;
std::getline(is,input);
// C++11 version
return stoi(input); // throws on failure
// C++98 version
/*
std::istringstream iss(input);
int i;
if (!(iss >> i)) {
// handle error somehow
}
return i;
*/
}
You can create a similar function for floats, doubles and other things. Then when you need in int, instead of this:
cin >> i;
You do this:
i = getInt(cin);
Its because you have a '\n' left lying on the input stream from a previous call.
cin >> i; // This reads the number but the '\n' you hit after the number
// is still on the input.
The easiest way to do interactive user input is to make sure each line is processed independently (as the user will hit enter after each prompt).
As a result always read a line, then process the line (until you get familiar with the streams).
std::string line;
std::getline(std::cin, line);
std::stringstream linestream(line);
// Now processes linestream.
std::string garbage;
lienstream >> i >> garbage; // You may want to check for garbage after the number.
if (!garbage.empty())
{
std::cout << "Error\n";
}
Ignore some characters until line feed is reached.
cin.ignore(256, '\n')
getline (cin,mystr);