C++: Troubleshooting while loop with nested if/else statement - c++

I'm learning C++ and have a few questions.
This program should take inputs for the name and price of items and output them to a text file. When the sentinel value 999 is entered for the item name, the while loop should cease and output all sets of inputs (item name and price) to the text file.
I have two problems with this program:
Only the most recent set of inputs (name, price) is displayed. How do I keep all inputs in memory?
Entering 999 for the item name does not cause the program to exit. Instead, the program ceases to display prompts. How do I stop the program correctly?
I should probably use a for loop, but I'm not sure how that would be implemented.
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
string item_name;
double price;
int item_number;
const string SENTINEL = "999";
ofstream myfile ("invoice1.txt");
while(item_name != SENTINEL)
{
cout<<"Enter the name of the item."<<'\n';
cin>>item_name;
if (item_name == SENTINEL)
{
cin>>item_name;
myfile<<"Thank you for your entries"<<'\n';
myfile<<item_name<<"#"<<price<<endl;
myfile.close();
break;
}
else
{
cout<<"Enter the price of the item."<<'\n';
cin>>price;
}
}
myfile<<"Thank you for your entries"<<'\n';
myfile<<item_name<<"#"<<price<<endl;
myfile.close();
return 0;
}

How to do this with a std::vector
First, a few includes:
#include <vector> // obviously. Can't do vectors without the vector header.
#include <limits> // for a trick I'll use later
Create a structure to link the item name with a price
struct item
{
string name;
double price;
};
and make a vector of that structure
vector<item> items;
Then after you read in a name and price, stuff it in a structure and stuff the structure into the vector.
item temp;
temp.name = item_name;
temp.price = price;
items.push_back(temp);
On why the while loop doesn't work... That'll take a walk through.
while(item_name != SENTINEL)
This is a good start. If item_name isn't SENTINEL, keep going. Exactly right. Thing is, item-name hasn't been set the first time you get here, forcing some squirrelly logic inside the loop. General rule of thumb is read, then test. Test before read is not so useful. For one thing, there's nothing to test, but the real problem is now you're using untested data or have to include another test to catch it.
Read, then test.
{
cout<<"Enter the name of the item."<<'\n';
cin>>item_name;
Get an item name. Groovy-ish.
if (item_name == SENTINEL)
{
cin>>item_name;
OK. Not bad, but why get another item_name here?
myfile<<"Thank you for your entries"<<'\n';
myfile<<item_name<<"#"<<price<<endl;
myfile.close();
break;
break exits a loop or a switch. So out we go.
}
else
{
cout<<"Enter the price of the item."<<'\n';
cin>>price;
Reading in numerical values has a few dangers you have to watch out for. The big one is if whatever the user typed can't be turned into price, cin goes into error mode and won't get back out until the error is cleared. And before you try to get price again, the garbage data needs to be removed.
A neat thing about cin >> x is it returns cin. This lets you stack commands. cin>>a>>b>>c>>d. cin, and all its streaming brethren, have a built in boolean operator you can use in tests. If cin is still in a good state, all of the reads completed successfully, it can be tested and will return true.
This lets you do stuff like if (cin>>a>>b>>c>>d) and test that all of the reads were good in one shot.
}
}
Applying read, then test, we get
while(cin>>item_name && // read in an item name
item_name != SENTINEL) // and then test that it isn't the sentinel
This goofy-looking bit of code is about the safest way to do this. It will even catch and exit cin comes to a sudden end. Doesn't happen all that often with cin, but this is a great way to test for the end of a file.
{
while (!(cin >> price)) // keep looping until the user gives a number
{ // if we're here, the user didn't give a good number and we have to clean up
// after them. Bad user. Bad. Bad.
As an aside, don't do this trick with a file. The file could have ended. You would have to test the file for end of file and exit here before continuing with the clean up.
// clear the error
cin.clear();
// throw out everything the user's typed in up to the next line
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
// user got out of the while of doom. They must have entered a number.
Welllll actually, it just had to start with a number. cin >> is pretty dumb and will let 1234abcd through, taking the 1234 and leaving the abcd for the next read. This can leave you with some nasty surprises. In this case the abcd will wind up as the next item_name. what should have been the next item_name will become the next price and the bad ju-ju rolls on from there.
And now back to the code.
item temp; // make a temporary item
temp.name = item_name; // set the values correctly
temp.price = price;
items.push_back(temp); // put them in the list.
You can save a bit of work here by adding a constructor to item and using vector's emplace_back method. Look it up. Very handy.
}
And again without the running commentary:
while(cin>>item_name && // read in an item name
item_name != SENTINEL) // and then test that it isn't the sentinel
{
while (!(cin >> price)) // keep looping until the user gives a number
{ // if we're here, the user didn't give a good number and we have to clean up
// after them. Bad user. Bad. Bad.
// clear the error
cin.clear();
// throw out everything the user's typed in up to the next line
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
// user got out of the while of doom. They must have entered a number.
item temp; // make a temporary item
temp.name = item_name; // set the values correctly
temp.price = price;
items.push_back(temp); // put them in the list.
}
Now you have a vector full of items to print. Stack Overflow is full of examples on how to do this, but it's best if you take a shot at it first.

Why do you have an extra cin call inside the if statement checking the entry?
I think that is not necessary.
For the issue with your inputs you are only storing the most recent entered values because each time the loop runs again it will over write the variables.
To solve this you will need to use an array to store items. If you want to make it so that you can run the loop an enter as many inputs as needed you will need to implement a dynamic array.
Here is how to implement a dynamic array http://www.learncpp.com/cpp-tutorial/6-9a-dynamically-allocating-arrays/

Related

How to implement an "ENTER" case into a switch statement

I am working on a class project where I have to create an ordering system for a coffee shop in C++. If it is applicable, I'm working in Visual Studio.
In the project outline, the teacher said that there is a simple integer input to navigate the menu; however, he specifies that if NOTHING is inputted (I'm assuming what I've seen called a "hot enter") that it calculates the receipt and the program resets.
I have tried cin.get() and checking if the buffer is '\n', and this works fine, but my current implementation seems to only be able to capture a hot enter, and fails to roll into the switch case.
For getting input from the user, I've currently tried this:
// Get menu input
if (cin.get() == '\n') { // Check if user hot entered, assign value if so
input = 0;
} else { // If not, do it normally
input = cin.get();
}
However this does not work quite right, and fails to capture inputted integers for use in the switch case. I'm unsure if this sort of implementation is sound in reasoning, or whether there is a much simpler route to have a case for a hot enter.
I don't receive any errors, so I imagine there is something wrong with my understanding of how these functions work, or my implementation is flawed in its logic.
Thank you.
You used cin.get() twice. The second cin.get() in input = cin.get(); is redundant.
// Get menu input
input = cin.get();
if (input == '\n') { // Check if user hot entered, assign value if so
input = 0;
}
//else { // If not, do it normally
// input = cin.get();
// }

searching a name in the csv file on C++

I am a young programmer who is trying to learn c++. i have a working csv.file. but i want to search for a specific number assigned to the name and then displays the name of what i'm looking for. i have the file here:
1,Bulbasaur,grass
2,Ivysaur, grass
3,Venusaur, grass
4,Charmander, fire
5,Charmeleon, fire
6,Charizard, fire
7,Squirtle, water
8,Wartortle, water
9,Blastoise, water
Code
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream ip("pokedex.csv");
string pokedexnum[9];
string pokemonName[9];
string pokemonType[9];
cout<<"please enter a pokemon number:"<<" ";
cin>>pokemonType[0];
while (ip.good()){
getline( ip, pokedexnum[0]);
getline( ip, pokemonName[0]);
getline( ip, pokemonType[0]);
}
cout<<"the pokemon that is:"<< " "<<pokedexnum[0]<< "is the pokemon called:"<< pokemonName[0];
ifstream close("pokedex.csv");
return 0;
}
when it runs
please enter a pokemon number: 1
the pokemon that is: is the pokemon called:8,Wartortle, water
could you please point out what i am doing wrong?
Among the issues in this code:
You're not using std::getline correctly for comma-separated data. The result is each pass is consuming three lines from your input file; not three values from each line.
You're also not using ip.good() correctly as a while-condition.
You're retaining your test value in the array, which will be overwritten on the first iteration pass, so it is lost.
You're ignoring potential IO failures with each std::getline invoke.
You're overwriting slot-0 in your arrays with each loop iteration.
Minor, ifstream close("pokedex.csv"); clearly isn't doing what you think it is. That just creates another fstream object called close on the given file name.
The later may be intentional for now, but clearly broken in the near future.
In reality, you don't need arrays for any of this. All you're doing is reading lines, and seem to want to test the input number against that of the CSV data first column, reporting the line that you find, then ending this.
So do that:
Read the input value to search for.
Open the file for scanning.
Enumerate the file one line at a time.
For each line from (3), use a string stream to break the line into the comma separated values.
Test the id value against the input from (1). If the same, report the result and break the loop; you're done.
The result is something like this:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>
int main()
{
std::cout<<"please enter a pokemon number: ";
long num;
if (std::cin >> num && num > 0)
{
std::ifstream ip("pokedex.csv");
std::string line;
while (std::getline(ip, line))
{
std::istringstream iss(line);
std::string id, name, skill;
if (std::getline(iss, id, ',') &&
std::getline(iss, name, ',') &&
std::getline(iss, skill))
{
char *endp = nullptr;
long n = std::strtol(id.c_str(), &endp, 10);
if (id.c_str() != endp && n == num)
{
std::cout << "The pokemon that is: " << num << " is called: " << name << '\n';
break;
}
}
}
}
}
Admittedly untested, but it should work.
Whether you want to store the items in arrays at this point is entirely up to you, but it isn't needed to solve the somewhat abstract problem you seem to be attempting, namely finding the matching line and reporting the name from said-same. If you still want to store them in arrays, I suggest you craft a structure to do so, something like:
struct Pokemon
{
int id;
std::string name;
std::string skill;
};
and have a single array of those, rather than three arbitrary arrays that must be kept in sync.
Four issues jump out at me:
You store the user's input into pokemonType, but then also use pokemonType for reading data from your CSV file. The file input is going to overwrite the user input.
Your file input loop always references index 0. All of the lines from your data file are going into element 0. That's the main reason that even if the user inputs 1, the output is from the last line of the data file.
Your file reading loop is structured like you want to put one part of each data line into a different array, but what you've written actually reads three lines on every iteration, storing those lines into the three different arrays.
This isn't affecting your output, but the code ifstream close("pokedex.csv"); is written like you want to close the file stream you opened, but I do believe what this line actually does is create a new ifstream called close, and opens pokedex.csv attached to it. In other words, it's just like your other line ifstream ip("pokedex.csv"); but with close as the variable name instead of ip.
You are going to want to look into something called "string tokenization". Start with some web searches, apply what you read about to your code, and of course if you hit another snag, post a new question here to Stack Overflow, showing (as you did here) what you tried and in what way it isn't working.
Elaborating on #3, here's what how your data file is being read:
at the end of the 1st iteration of the file-reading loop, ...
pokedexnum[0] is "1,Bulbasaur,grass"
pokemonName[0] is "2,Ivysaur, grass"
pokemonType[0] is "3,Venusaur, grass"
at the end of the 2nd iteration of the file-reading loop, ...
pokedexnum[0] is "4,Charmander, fire"
pokemonName[0] is "5,Charmeleon, fire"
pokemonType[0] is "6,Charizard, fire"
at the end of the 3rd iteration of the file-reading loop, ...
pokedexnum[0] is "7,Squirtle, water"
pokemonName[0] is "8,Wartortle, water"
pokemonType[0] is "9,Blastoise, water"
And that's why
<< "is the pokemon called:"<< pokemonName[0];
outputs
is the pokemon called:8,Wartortle, water

Unexpected infinite loop (C++)

while(true)
{
unsigned int option;
cout<<"1 - Display the list\n";
cout<<"2 - Add a game title to the list\n";
cout<<"3 - Remove a game title from the list\n";
cout<<"4 - Exit\n";
cin>>option;
if(option == 1)
{
if(gameTitles.empty())
{
cout<<"\nThere are no games to be displayed. Please try again after adding some games to the list.\n";
}
else
{
for(iter = gameTitles.begin(); iter != gameTitles.end(); ++iter)
{
cout<<*iter<<endl;
}
}
}
else if(option == 2)
{
cout<<"\nEnter the game's title:\n";
cin>>newGame;
gameTitles.push_back("newGame");
}
else if(option == 3)
{
cout<<"\nEnter a game to be removed:\n";
cin>>removeGame;
theIterator = find(gameTitles.begin(),gameTitles.end(),removeGame);
theIterator = gameTitles.erase(theIterator);
}
else if(option == 4)
{
break;
}
else
{
cout<<"\nThe option is illegal. Please try again.\n";
}
}
When I choose any 1, 3, 4 or illegal option, the loop brings me to the top and I have possibility to choose again. The problem arises when I am trying to use 2nd option. I just enter in an infinite loop. But I want to enter a game title and after it is added to my vector (I declared it earlier) and then have possibility to choose an option again.
You don't show the type of newGame but I would guess that it is of type std::string and you enter a title with two words: the stream reads the first word and stops reading. The next thing you do is read an int which fails and leaves the value of option unchanged. From then on the stream won't do anything and just keeps reading.
The key error is not checking that the read attempt was successful before using the result: you always need to verify that your input was successful after reading and before using the result. When an input operation fails the stream goes into failure mode, i.e., std::ios_base::failbit gets set, and it converts to false instead of true (well, actually it converts to a null pointer vs. a non-null pointer but that's a detail irrelevant to this discussion). Once in failure state the stream won't do anything until the stream state is clear()ed. You you probably also need to ignore() the offending characters.
That is, you certainly should use something like
if (std::cin >> option) {
// do something with the option
}
else {
std::cout << "expected an integer: ignoring the line\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
To read the title you should probably read an entire line. Since the formatted input for the option will leave the newline character in the stream, you will first need to skip that character, though. That is, the input for the new title would look something like this:
if (std::getline(std::cin >> std::ws, newGame)) {
// ...
}
The std::ws manipulator skips all leading whitespace. That is probably OK for your needs. If the string being read can have leading whitespace characters something different will be needed.
Before you try to cin again to the same variable newgame, put in a cin.ignore(). If I recall correctly, the first time you cin to a string (I'm assuming newgame is a string), it leaves a trailing \n so it will just automatically enter through in later prompts.

stop inupt if input no double

first of all I'm sorry for my
bad english.
I'm trying to read some numbers and write them into a vector in C++.
This should go as long as the input is a double number and the
loop should be stopped if the user writes an 'a'.
My Question is how can I check if the input is 'a'.
Breaking the loop is not the problem
while(true){
if(!(cin>>userInput)){
//here i want to know if the input is 'a' or some other stuff//
//also i want to do some other stuff like printing everything//
//what already is in the vector//
//when everything is done; break//
}
else
//the input is a valid number and i push it into my vector//
'userInput' is defined as double so the loop will stop.
My Problem is, if the user write 'q' the loop stops but it's instantly stoping the whole program. My try look like this:
while(true){ //read as long as you can
cout<<"Input a number. With 'q' you can stop: "<<endl;
if(!(cin>>userInput)){ //here the progam stops when the input is anything but a number
cout<<"How many numbers do you want to add up?"<<endl; //there are numbers in a vector that should be added up
cin>>numberOfAdditions;
break;
}
So I have a vector with some numbers the users writes down (20,50,90,...)
When the input is equal to 'q' (in this example everything but numbers )
the loop stops and I want to ask the user how many numbers should be added.
The cout-command is displayed but the input is beeing skipped.
So my program is not reading how many valued from the vector I want to add.
I hope you know what I mean and I don't want to use two questions and two variables to save the input but if it's not working without it I'll change my program.
Have a nice Day :)
Because your Input variable is of type double you have to flush the Input from cin before reading again. Otherwise there is still a newline in the buffer.
Consider the following example:
#include <iostream>
using namespace std;
int main(){
double userInput;
int numberOfAdditions;
while(true){ //read as long as you can
cout<<"Input a number. With 'q' you can stop: "<<endl;
if(!(cin>>userInput)){ //here the progam stops when the input is anything but a number
cout<<"How many numbers do you want to add up?"<<endl; //there are numbers in a vector that should be added up
cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
cin.clear();
cin.ignore(INT_MAX,'\n');
cin >> numberOfAdditions;
break;
}
}
return 0;
}
The two Statements:
cin.clear();
cin.ignore(INT_MAX,'\n');
are flushing the Input stream until the newline is encountered.
The first answer already explains how to flush the cin stream after the user types in a char.
If you want to determine which character it was, you should define userInput as std::string. If the string is not "q" or "a" or whatever you are looking for, you have to cast the string to a double, just like this:
std::string str;
cin >> str;
if (str == "j")
// User typed in a special character
// ...some code...
else
double d = atof(str.c_str()); // Cast user input to double
Notice that the result of the cast is zero, if the user typed in any other string than the ones you especially look for.

read in values and store in list in c++

i have a text file with data like the following:
name
weight
groupcode
name
weight
groupcode
name
weight
groupcode
now i want write the data of all persons into a output file till the maximum weight of 10000 kg is reached.
currently i have this:
void loadData(){
ifstream readFile( "inFile.txt" );
if( !readFile.is_open() )
{
cout << "Cannot open file" << endl;
}
else
{
cout << "Open file" << endl;
}
char row[30]; // max length of a value
while(readFile.getline (row, 50))
{
cout << row << endl;
// how can i store the data into a list and also calculating the total weight?
}
readFile.close();
}
i work with visual studio 2010 professional!
because i am a c++ beginner there could be is a better way! i am open for any idea's and suggestions
thanks in advance!
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <limits>
struct entry
{
entry()
: weight()
{ }
std::string name;
int weight; // kg
std::string group_code;
};
// content of data.txt
// (without leading space)
//
// John
// 80
// Wrestler
//
// Joe
// 75
// Cowboy
int main()
{
std::ifstream stream("data.txt");
if (stream)
{
std::vector<entry> entries;
const int limit_total_weight = 10000; // kg
int total_weight = 0; // kg
entry current;
while (std::getline(stream, current.name) &&
stream >> current.weight &&
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n') && // skip the rest of the line containing the weight
std::getline(stream, current.group_code))
{
entries.push_back(current);
total_weight += current.weight;
if (total_weight > limit_total_weight)
{
break;
}
// ignore empty line
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
else
{
std::cerr << "could not open the file" << std::endl;
}
}
Edit: Since you wannt to write the entries to a file, just stream out the entries instead of storing them in the vector. And of course you could overload the operator >> and operator << for the entry type.
Well here's a clue. Do you see the mismatch between your code and your problem description? In your problem description you have the data in groups of four lines, name, weight, groupcode, and a blank line. But in your code you only read one line each time round your loop, you should read four lines each time round your loop. So something like this
char name[30];
char weight[30];
char groupcode[30];
char blank[30];
while (readFile.getline (name, 30) &&
readFile.getline (weight, 30) &&
readFile.getline (groupcode, 30) &&
readFile.getline (blank, 30))
{
// now do something with name, weight and groupcode
}
Not perfect by a long way, but hopefully will get you started on the right track. Remember the structure of your code should match the structure of your problem description.
Have two file pointers, try reading input file and keep writing to o/p file. Meanwhile have a counter and keep incrementing with weight. When weight >= 10k, break the loop. By then you will have required data in o/p file.
Use this link for list of I/O APIs:
http://msdn.microsoft.com/en-us/library/aa364232(v=VS.85).aspx
If you want to struggle through things to build a working program on your own, read this. If you'd rather learn by example and study a strong example of C++ input/output, I'd definitely suggest poring over Simon's code.
First things first: You created a row buffer with 30 characters when you wrote, "char row[30];"
In the next line, you should change the readFile.getline(row, 50) call to readFile.getline(row, 30). Otherwise, it will try to read in 50 characters, and if someone has a name longer than 30, the memory past the buffer will become corrupted. So, that's a no-no. ;)
If you want to learn C++, I would strongly suggest that you use the standard library for I/O rather than the Microsoft-specific libraries that rplusg suggested. You're on the right track with ifstream and getline. If you want to learn pure C++, Simon has the right idea in his comment about switching out the character array for an std::string.
Anyway, john gave good advice about structuring your program around the problem description. As he said, you will want to read four lines with every iteration of the loop. When you read the weight line, you will want to find a way to get numerical output from it (if you're sticking with the character array, try http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/, or try http://www.cplusplus.com/reference/clibrary/cstdlib/atof/ for non-whole numbers). Then you can add that to a running weight total. Each iteration, output data to a file as required, and once your weight total >= 10000, that's when you know to break out of the loop.
However, you might not want to use getline inside of your while condition at all: Since you have to use getline four times each loop iteration, you would either have to use something similar to Simon's code or store your results in four separate buffers if you did it that way (otherwise, you won't have time to read the weight and print out the line before the next line is read in!).
Instead, you can also structure the loop to be while(total <= 10000) or something similar. In that case, you can use four sets of if(readFile.getline(row, 30)) inside of the loop, and you'll be able to read in the weight and print things out in between each set. The loop will end automatically after the iteration that pushes the total weight over 10000...but you should also break out of it if you reach the end of the file, or you'll be stuck in a loop for all eternity. :p
Good luck!