Deleting content from a file [closed] - c++

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I am making a program that allows a user to make bank accounts and save them too a file, you can also delete them. I am having issues with my code for deleting an account on the file, my function for deleting the account looks like this.
int deleteCustomer(account acc[], int numCust)
{
string target;
bool accFound = false;
int count = 0;
cout << "Enter account number: ";
cin >> target;
for (int i = 0; i < numCust; i++)
{
if (acc[i].acctNum == target)
{
accFound = true;
break;
}
count++;
}
if (accFound == false)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else if (accFound == true)
{
ofstream outFile;
outFile.open("customer.dat");
for (int i = count; i < numCust - 1; i++)
{
outFile << acc[i+1].acctNum;
outFile << '#';
outFile << acc[i+1].name;
outFile << '#';
outFile << acc[i+1].cBal;
outFile << '#';
outFile << acc[i+1].sBal;
outFile << '#';
}
numCust--;
outFile.close();
}
return numCust;
}
The function is supposed to overwrite the account selected by the user by saving the file ahead of it to the previous spot and return the new number of customers. It appears to run through fine but it does not do anything and I am uncertain as to why. Any input would be helpful, thank you.

Several problems here:
Your account lookup should be working, but you're overcomplicating this a bit (you'd only need one value rather than three, but let's skip that for now). If you're interested let me know.
You're never actually removing any account (just reducing the number of total accounts; which will then cause the last entry to be removed).
When saving you accounts to the file, you start at that selected index, which doesn't make any sense at all.
Let's assume you've got 10 accounts, indices 0 through 9.
The user picks the account at index 5.
You save accounts index 6 through 9(!) only.
The user picks the account at index 0.
You save accounts index 1 through 9 only.
Some style things:
You essentially store the selected account's index in count. That's fine, but very misleading. Don't ever use misleading variable names. As you might be able to tell from my comment above, I misread that part as well.
Rather than writing if (booleanValue == true) you could just write if (booleanValue), which results in the same code, but is shorter and might be faster to read. In a similar way, you could replace if (booleanValue == false) with !booleanValue.
Don't omit namespaces like std, if you can (e.g. use std::string rather than string and avoid using namespace std;) to avoid writing ambigious code. If some other namespace you use has string (or any other member) as well, you'll either have to explicitly name the namespace anyway or you're at least confusing others reading your code. Also there's always the potential bug introduced by unintentionally using a different type.
Fixing the actual problem:
I assume this is some homework assignment or some tutorial/class code or anything similar? If so, don't just copy the following code and instead try to think about how it's working. Once you understood, implement it yourself and only use my snippets if you're really stuck.
In general, it's good software design to keep code and functions minimal. Don't create "super functions" that do several things. Also try to make code reusable, so in case you change something, you're able to adjust it in one place only.
Take your code above for example. Whenever you add, delete, or update an account, you'll have to write the new file. Did you plan on replicating the same code multiple times? If you'd have to adjust your file format, you'd have to change it everywhere.
You'll also need some way to actually remove customer datasets. As you might be aware, deleteing entries in an array would require you to move all entries behind it (to keep it continguous). This can be a very expensive operation.
To avoid this, I'm adding a new member bool valid to account. By default, this is set to false. Once there's some data put there (either through reading from a file or by the user), it's value is set to true.
So instead split this into two separate functions (moving the common code - saving - to its own function):
// By returning an integer value, you're able to communicate issues or problems
// without having to rely on exceptions (in case you're using C++).
// Note that I don't do any error checking here for simplicity.
// Parameters:
// filename - the target file to write
// acc - the array holding all customer accounts
// size - the maximum amount of values in acc
// Return value: 0, if everything went fine
// (I skipped actual error handling to keep it simple!)
int saveCustomers(const char *filename, account acc[], int size) {
std::ofstream outFile(filename);
// Iterate over all entries
for (int i = 0; i < num; ++i) {
// Do we actually have to store the account?
if (acc[i].valid) {
outfile << acc[i].acctNum << '#' << acc[i].name; // write all the values the way you did
}
}
outFile.close();
return 0; // Everything ok
}
Now that this is done, you're able to create your functions to modify your customer data:
int deleteCustomerByNumber(account acc[], int num, std::string target) {
// Iterate over all accounts and look for the selected one
for (int i = 0; i < num; ++i) {
// Only check valid accounts and see whether it's the target
if (acc[i].valid && acc[i].acctNum == target) {
acc[i].valid = false; // Mark it as invalid
return 0; // Everything ok
}
}
return 1; // Didn't find it!
}
In a similar way you can look for empty/unused entries to actually write data to them.
Bonus - alternative (STL) approach:
Since you're using C++, I'd suggest you use a different data structure, not just a simple array:
If you use a STL container (more specific: a map), you're able to handle everything a lot easier.
#include <map>
// Create a typedef to simplify expressions
typedef std::map<std::string, account> accmap;
// All accounts would be stored in this object:
accmap accounts;
// To do a quick lookup of any account:
accmap::const_iterator a = accounts.find(accountNumber);
if (a == accounts.end())
;// Account not found!
else {
a->first; // This is your account number
a->second; // This is your `account` object
}
// To delete a specific account:
accounts.erase(accountNumber)
// To create a new account simply access it:
accounts[accountNumber].name = newName;

You need to save all of the records before the index and after the index, otherwise you are effectively deleting more than just the one account. Presumably you should also remove the record from the input array as well. You are also not doing any error handling on the input or output. And you need to fix your output loop, it is not using indexes correctly.
Try this:
int deleteCustomer(account acc[], int numCust)
{
string target;
int accFound = -1;
cout << "Enter account number: ";
if (cin >> target)
{
for (int i = 0; i < numCust; ++i)
{
if (acc[i].acctNum == target)
{
accFound = i;
break;
}
}
}
if (accFound == -1)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else
{
for (int i = accFound+1; i < numCust; ++i)
acc[i-1] = acc[i];
--numCust;
ofstream outFile;
outFile.open("customer.dat");
for (int i = 0; (i < numCust) && (outFile); ++i)
{
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}
if (!outFile)
cout << "Error saving customer file" << endl;
}
return numCust;
}
If you don't want to update the array, then you can do this instead:
int deleteCustomer(account acc[], int numCust)
{
string target;
int accFound = -1;
cout << "Enter account number: ";
if (cin >> target)
{
for (int i = 0; i < numCust; ++i)
{
if (acc[i].acctNum == target)
{
accFound = i;
break;
}
}
}
if (accFound == -1)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else
{
ofstream outFile;
outFile.open("customer.dat");
for (int i = 0; (i < numCust) && (outFile); ++i)
{
if (i != accFound)
{
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}
}
if (!outFile)
Cout << "Error saving customer file" << endl;
--numCust;
}
return numCust;
}
Lastly, when updating a file, it is a good idea to write the new data to a temp file first, then replace the original file with the temp file only if everything is successful. That way you reduce the risk of corrupting the original file.

To "deleting an account on the file", this part of code:
for (int i = count; i < numCust - 1; i++)
{
outFile << acc[i+1].acctNum;
outFile << '#';
outFile << acc[i+1].name;
outFile << '#';
outFile << acc[i+1].cBal;
outFile << '#';
outFile << acc[i+1].sBal;
outFile << '#';
}
should be
for (int i = 0; i < numCust; i++)
{
if(i == count) continue;// remove the account user selected
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}

Related

Add a Data value to the end of an array in c++

I need some help with this.
I am running through some payroll data.
I have run a function that reads a .txt file, and then stores it in arrays. I am now trying to write a function that allows you to add to those arrays (they are arrays of 32 but only have 20 elements from the file) So it needs to loop through the arrays, make sure they do not go out of bounds, and then add the additional data types to the arrays. Here is how it looks for me. There are no errors, but when i run it, it does nothing:
string AddPayrollData(array<double, kMaxSize> &hours,
array<char, kMaxSize> &pay_type,
array<double, kMaxSize> &pay_rate,
array<string, kMaxSize> &names,
double hours_worked, char pay_t, double pay_r, string full_name,
int &size )
{
int i = 0;
while (size < kMaxSize && i < size)
{
if (hours[i] > 168)
{
cout << "Hours is out of range, set to 0" << endl;
hours[i] = 0;
}
if (pay_type[i] != 'h' || 's')
{
cout << "Incorrect pay type, set to s " << endl;
pay_type[i] = 's';
}
if (pay_rate[i] < 7.25)
{
cout << "Pay is less than minimum wage, set to 7.25 " << endl;
pay_rate[i] = 7.25;
}
if (names[i].find(","))
{}
else
{
cout << "Name Is not in last, first format" << endl;
names[size] = "unknown";
}
i++;
}
if(size >= kMaxSize)
{
cout << "Arrays are full, no payroll data added" << endl;
}
else
{
hours_worked = hours[size];
pay_t = pay_type[size];
pay_r = pay_rate[size];
full_name = names[size];
size++;
}
return "";
}
Thanks for the help!
There seem to be a few issues here. Some have been dealt with in the comments above. So I will mention the others.
The most important issue is that the purpose of the function is confused. It is both validating the existing data and adding (but not validating) new data. Nothing wrong with either of these things, but they should be separate functions. Or maybe this function should add and validate the new data but leave the existing data alone.
In the validating part, there are some mistakes already mentioned, plus names[size] = "unknown"; which should be names[i] = "unknown";.
In the adding part the problem is that the assignments are performed the wrong way round
hours_worked = hours[size];
pay_t = pay_type[size];
pay_r = pay_rate[size];
full_name = names[size];
should of course be
hours[size] = hours_worked;
pay_type[size] = pay_t;
pay_rate[size] = pay_r;
names[size] = full_name;
Finally for some strange reason the function returns a string. Just make it a void function.

Using strcmp() to compare two arrays of C-strings

My project is to make a bank account program where the user enters an account number and a password to do anything within the program. The account numbers and passwords used must be stored as C-strings (the string header file is not allowed). I believe that the problem I am having is with the strcmp function. Here is my function where the problem occurs.
void get_password(int num_accounts, char **acc_num, char **password)
{
char account[ACCOUNT_NUMBER];
char user_password[PASS_LENGTH];
std::cout << "\nEnter the account number: ";
// std::cin.getline(account, ACCOUNT_NUMBER);
std::cin >> account;
int i = 0;
do
{
if (strcmp(account, *(acc_num + i)) != 0)
{
i++;
}
else
break;
} while (i <= num_accounts);
if (i == num_accounts)
{
std::cout << "\nCould not find the account number you entered...\nExiting the program";
exit(1);// account number not found
}
std::cout << "\nEnter the password: ";
// std::cin.getline(user_password, PASS_LENGTH);
std::cin >> user_password;
if (strcmp(user_password, *(password + i)) != 0)
{
std::cout << "\nInvalid password...\nExiting the program";
exit(1);// incorrect password
}
else
{
std::cout << "\nAccount number: " << account
<< "\nPassword: " << user_password << "\n";
return;
}
}
acc_num and password are both arrays of C-strings. When I run/debug the program, it crashes at the first if statement. I guess my question is whether I'm using the strcmp function correctly or not, or if there is a problem with the pointers that I am using.
Your loop will run even when num_accounts is 0. Also, you're doing an out-of-bound array access by writing while (i <= num_accounts); instead of while (i < num_accounts);.
It would be better to write it like this:
while (i < num_accounts)
{
if (strcmp(account, *(acc_num + i)) == 0)
{
// match found!
break;
}
i++;
}
You're assuming there is at least one account, and you're also looping once too often. A safer way to write it would be as follows:
for (int i = 0; i < num_accounts && !strcmp(account, accnum[i]); i++)
;
or the corresponding while loop. A do/while is not appropriate here.

C++ Save a text document

I've been working on my game and I can easily save my text document, everything works perfectly. My question is, how can I save the text document into a file called "Saves." Here is my code.
Also! I get the input for the char* name from
Save(gets(new char [50]));
Why won't this code work right here?
char* newArray = new char[strlen("PaintAPicture/")+strlen("Saves/")+strlen(name)+strlen(".asciip")+1];
strcpy(newArray,"PaintAPicture/");
strcpy(newArray,"Saves/");
strcpy(newArray,name);
strcat(newArray,".asciip");
I took what you said about using a string, but it's not creating the file and I get the Saving Failed, Main Problem error.
if(saveFile)
{
system("cls");
string prename;
cout << "Enter level's number: ";
cin >> prename;
string name = "Files/" + "Saves/" + prename + ".asciip";
ofstream out(name, ios::binary);
if (!out.is_open()){ MessageBox( 0, "Saving failed! Main problem.", 0, MB_ICONERROR); system("cls"); RedrawMap(); return 0; }
system("cls"); cout << "Saving...";
system("cls");
ShowConsoleCursor(false);
cout << "Saving...";
Sleep(1000);
for(int i = 9; i < SizeY; i++)
{
for(int j = -1; j < SizeX; j++)
{
out << Map[i][j].ch << endl;
out << (int)Map[i][j].color << endl;
}
}
}
cout << '\a';
out.close();
}
Prepend the filename with "Saves/"
char* newArray = new char[strlen("Saves/")+strlen(name)+strlen(".asciip")+1];
strcpy(newArray,"Saves/");
strcat(newArray,name);
strcat(newArray,".asciip");
Also std::string class is designed for storing and manipulating strings. Much easier to use and a lot less error prone than C-strings. Info on string.
EDIT:
Your first piece of code
char* newArray = new char[strlen("PaintAPicture/")+strlen("Saves/")+strlen(name)+strlen(".asciip")+1];
strcpy(newArray,"PaintAPicture/");
strcpy(newArray,"Saves/");
strcpy(newArray,name);
strcat(newArray,".asciip");
doesn't work becouse you're using strcpy where you should use strcat.
strcpy(newArray,"Saves/");
strcpy(newArray,name);
should be
strcat(newArray,"Saves/");
strcat(newArray,name);
As for the problem with creating the file, do those folders exist already? ofstream can create new files to a specified folder, but it cannot create new folders. See https://stackoverflow.com/a/9089919/4761271

c++ for loop with decrement update causing infinite loop?

This is part of a greater code for reading an input file word-for-word, then printing the words in reverse order. It uses a string array called words[] to store, word-by-word, the char strings from an input file earlier in the program:
//print to screen
for (int i = MAXSIZE; i >= 0; i--)
{
cout << words[i] << " ";
}
Test input file contents:
This is my test file. I hope this works.
Output is just "works. " repeating on and on.
Why is the i-- apparently never happening?
EDIT: Everything from my code. I'm on a bit of a time crunch here, to say the least. MAXSIZE=1024 part of lab prompt. Can't use vectors or reverse; seen that all over, but it's off limits for this lab. New to programming, so if you could refrain from being condescending, that'd be great. Just trying to get this to work. The reading input.txt and print to screen bit works fine. Output portion is utter fail and I don't know why. Can someone just tell me why instead of insulting me, thanks?
//Kristen Korz
//CIS 22A
//This program reads an input file and writes the words in reverse order to an output file.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
//create and link input...
ifstream inputFile;
inputFile.open("input.txt");
//...and output files
ofstream outputFile;
outputFile.open("output.txt");
//error message for file open fail
if (inputFile.fail())
cout << "Error opening the file.\n";
//constant for max size
const int MAXSIZE = 1024;
//string array and temporary-use string
string words[MAXSIZE];
string str; //note: variables will be used for output loops too
//read words from input file
for (int i = 0; (inputFile >> str) && (i < MAXSIZE); ++i)
{
words[i] = str;
//for showing in terminal if read correctly
cout << words[i] << " ";
}
inputFile.close();
cout << endl;
//something wrong with for loop resulting in i apparently not updating
for (int i = MAXSIZE; (outputFile << str) && (i >= 0); --i)
{
words[i] = str;
//for showing in terminal if written correctly
cout << words[i] << " ";
}
outputFile.close();
cout << endl;
system("pause");
return 0;
}
For output with i also printed, my cout statements in the for-loops say:
cout << words[i] << " " << i << " ";
Giving terminal output:
This 0 is 1 my 2 test 3 file. 4 I 5 hope 6 this 7 works. 8
works. 1023 works. 1022 works. 1021 (lots of repeats of works. followed by decrementing numbers) works. 3 works. 2 works. 1 works. 0
Your output loop does:
words[i] = str;
for every iteration. str still holds the value of the last string you input, so this sets every member of words to be the same string. Since your last input string was "works", this explains why you output "works" every time.
It should work better if you just remove that line. Also, start from MAXSIZE - 1. The valid indices of the array are 0 through MAXSIZE-1. Your out-of-bounds access causes undefined behaviour, although apparently in this instance it had no effect.
However if your input only has 8 words as you suggest, then outputting 1024 words will give you a lot of blank space. Consider starting the output from where i got up to, instead of MAXSIZE - 1.
At the part marked as not working (the second for loop), str is being read from but it is never changed to anything else in that loop, so it repeats the last word. i is being updated, the problem is that str is not being updated.
The other issue is that you are trying to access an element past the end of the array, as WhozCraig and Velthune discussed in their answers. You need to properly figure out what you want to do with words in your second for loop. This is key. Also, you need to store where the array you read in ends.
Viewing WhozCraig's link, if you have:
const int MAXSIZE = 1024;
string words[MAXSIZE];
for (int i = MAXSIZE; i >= 0; i--) {
cout << words[i] << " ";
}
You have a string that from 0..1023.
Accessing words[1024] is potentially dangerous.
For iterate correctly your string do:
for (int i = MAXSIZE - 1; i >= 0; --i) {
cout << words[i] << " ";
}
By the way, when you fill words, add a control:
for (int i = 0; (inputFile >> str) && (i < MAXSIZE); ++i)) {
if(str.size() <= MAXSIZE) {
words[i] = str;
}
}
update
Be sure that your string in file:
"This is my test file. I hope this works. "
doesn't end with a space. To be sure, test adding "EOF" to your string:
"This is my test file. I hope this works.EOF"
Other, do your loop in this way:
int i = 0;
while(inputFile.good() && i < MAXSIZE) {
std::string word << inputFile;
if(!word.empty())
words[i] = str;
//for showing in terminal if read correctly
cout << words[i] << " ";
}
The problem why you get a lot of "works" here is:
After this piece of codes:
//read words from input file
for (int i = 0; (inputFile >> str) && (i < MAXSIZE); ++i)
{
words[i] = str;
//for showing in terminal if read correctly
cout << words[i] << " ";
}
inputFile.close();
cout << endl;
//str = "works";
The values of variable str is works.
After that, you set every elements in words by str. So every elements in the words now are the same value works.
for (int i = MAXSIZE; (outputFile << str) && (i >= 0); --i)
{
words[i] = str;//=="works"
//for showing in terminal if written correctly
cout << words[i] << " ";
}
outputFile.close();
cout << endl;

Need help deleting loaded array and clearing it from count

I have been messing around with some code. I've really just mashed things together and exploring ways to get what I want out of it.
Right now something that has been bothering me is that when I try to delete a user it leaves the user blank instead of deleting it completely. I would rather it delete/remove the line completely and adjust the count without removing any other user data.
EDIT:
The problem is that after I delete a user. When I list every user it displays a blank User: pass: etc instead of not listing those to begin with. if I were to save the file it would have 5 empty spaces. I'd rather it remove those lines completely as if they were never there to begin with.
This is how I load the code:
int Loadpdata()
{
ifstream fp_in;
if(count > 0)
{
cout << "pdata already Loaded.\nTotal of " << count << " users loaded." << endl;
return 0;
}
fp_in.open("p.data"); //Open user file
if(fp_in == NULL)
{
cout << "Could not open user file, exiting." << endl;
}
while(!fp_in.eof()) { //While there is something to read
getline(fp_in,userd[count]);
getline(fp_in,passd[count]); //Read a line from the file
getline(fp_in,aged[count]); //Read a line from the file
getline(fp_in,locationd[count]); //Read a line from the file
getline(fp_in,emaild[count]); //Read a line from the file
getline(fp_in,mid[count]); //Read a line from the file
cout << "User: " << userd[count] << " Loaded Successfully." << endl;
userstotal++;
count++;
}
fp_in.close(); //Done with the user file
cout << "Total Users Loaded: " << count << endl;
if(!count > 0)
{
cout << "Userlist is empty, exiting" << endl;
return -2;
}
return 0;
}
Now this is how I delete the user:
int Deletedata()
{
char user[80];
int logged_in=0;
while(!logged_in) { //Force user to login. Give a quit option if you'd like
cout << "Enter user name: ";
cin >> user;
int found = 0;
for(int x=0;x<count && !found;x++) { //For each user/password pair..
if(userd[x].compare(user) == 0) { //Matched the username
found = 1;
logged_in = 1;
userd[x].clear();
passd[x].clear();
aged[x].clear();
locationd[x].clear();
emaild[x].clear();
mid[x].clear();
}
}
if(!found) {
cout << "Invalid username!" << endl;
}
}
//Once we're done with that loop, they logged in successfully.
cout << "Deleted " << user << " Successfully." << endl;
return 0;
}
The more I think about this the more I realize I may have to scrap it and come up with a new format.
If you want to just remove one entry and not touch any of the other entries, you'll need to use a node-based container, e.g., a std::list<T> or a std::map<K, V> (the latter would match your needs of locating an object by a key anyway).
That said, if you want to stick with an array and fill the gap, you have two principle options:
If you don't care about the order of the objects, you could just std::swap() the to-be-removed object with the last copy and then remove the newly last element.
If the order matters to you, you'll need to move all the objects after the gap forward. The easiest way to do that is probably to use std::remove_if().
BTW, trying to read a file and checking in.eof() does not work reliable. In particular, when there are no more objects eof() may or may not be true and you don't check after reading whether the read was successful. Also note that eof() may never become true, e.g., if the file contains a format error and gets into fail state. You should read you data using something like
while (std::getline(in, userd[count])
&& std::getline(in, passd[count])
// ...
&& std::getline(in, mid)) {
++count;
}
Instead of clearing the element at position x, use erase on the vector (must be std::vector, and not C-array)