I'm trying to create a program for a simple contact book which takes information about a contact from the user and saves it in a file. When I re-run the program and open the same file next time, I obviously want all the contacts I added previously to still be there and not be overwritten if I choose to add new contacts (or just exit the program without adding any new contacts for that matter). But I can't get the file to save properly. I can get contacts added to a map, but depending on where I put the saveToFile() the file always overwrites contacts at some point, either after adding a new contact or between program runs.
Here is my class for the contact book and function calls in main:
#include <iostream>
#include <fstream>
#include <map>
#include <sstream>
struct Contact {
std::string name;
std::string address;
std::string email;
std::string number;
std::string bday;
std::string other;
}
class ContactBook {
private:
std::map<std::string, Contact> contacts;
public:
void ContactBook::loadFromFile(const std::string filename) {
std::ifstream file(filename);
if (!file) {
std::cout << "Unable to open file" << std::endl;
return;
}
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
Contact contact;
if (std::getline(iss, contact.name, '\n') &&
std::getline(iss, contact.address, '\n') &&
std::getline(iss, contact.email, '\n') &&
std::getline(iss, contact.number, '\n') &&
std::getline(iss, contact.bday, '\n') &&
std::getline(iss, contact.other, '\n')) {
contacts[contact.name] = contact;
}
}
file.close();
std::cout << "File " << filename << " loaded" << std::endl;
}
void ContactBook::addContact(const Contact& contact) {
contacts[contact.name] = contact;
}
void ContactBook::saveToFile(const std::string& filename) {
std::ofstream file;
file.open(filename);
for (const auto& pair : contacts) {
const Contact& contact = pair.second;
file << contact.name << '\n' <<
contact.address << '\n' <<
contact.email << '\n' <<
contact.number << '\n' <<
contact.bday << '\n' <<
contact.other << '\n';
} file.close();
}
void ContactBook::printFile(const std::string& filename) {
std::string line;
std::ifstream file;
file.open(filename);
if (file.is_open()) {
while(std::getline(file, line)) {
std::cout << line << '\n';
}
file.close();
}
}
}
int main() {
ContactBook contactBook;
contactBook.load("contacts.txt");
int alt;
bool exit = false;
while (!exit) {
std::cout << "1 - Add contact" << '\n' <<
"2 - Remove contact" << '\n' << //will add later
"3 - Search for contact" << '\n' << //will add later
"4 - Save and exit" << '\n' <<
"5 - Print contact list" << std::endl;
std::cin >> alt;
switch(alt) {
case 1: {Contact contact;
std::cin.ignore();
std::cout << "Name: ";
std::getline(std::cin, contact.name);
std::cout << "Address: ";
std::getline(std::cin, contact.address);
std::cout << "Email: ";
std::getline(std::cin, contact.email);
std::cout << "Phone number: ";
std::getline(std::cin, contact.number);
std::cout << "Birthday: ";
std::getline(std::cin, contact.bday);
std::cout << "Other info: ";
std::getline(std::cin, contact.other);
contactBook.addContact(contact);
}
break;
case 2: /*...*/
break;
case 3: /*..*/
break;
case 4: contactBook.saveToFile("contacts.txt");
exit = true;
break;
case 5: contactBook.printFile("contacts.txt");
break;
default: std::cout << "invalid input" << std::endl;
}
}
return 0;
}
As of now, if I add a contact, save, and re-run the program and choose print, the previously added contact will be printed correctly. However, if I save and exit a second time, then the next run the file will be empty. The first contact will also be gone if a add another contact during the same program run - if I save and exit the file will contain only the second contact.
Sorry if I'm making a super dumb and obvious mistake here - I'm a beginner. But I just can't figure it out and I've been stuck with this for hours to the point where I can't really focus on what I'm doing anymore, help much appreciated.
The way you are reading the file can never work, because by default std::getline()
stops reading when it encounters '\n'
, so it can never output a std::string
that has a '\n'
in it. But you are expecting multiple fields to be on a single line delimited by '\n'
, which is nonsense.
The 1st iteration of the while
loop will read in only the 1st contact's name
into the istringstream
, then the subsequent if
will fail to read in the remaining values since they don't exist in the istringstream
. Then the next while
iteration will read in the 1st contact's address
into the istringstream
, and the if
will subsequently fail. And so on.
To do what you are attempting, use a different delimiter instead, such as '\t'
, eg:
void ContactBook::loadFromFile(const std::string filename) {
std::ifstream file(filename);
if (!file) {
std::cout << "Unable to open file" << std::endl;
return;
}
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
Contact contact;
if (std::getline(iss, contact.name, '\t') &&
std::getline(iss, contact.address, '\t') &&
std::getline(iss, contact.email, '\t') &&
std::getline(iss, contact.number, '\t') &&
std::getline(iss, contact.bday, '\t') &&
std::getline(iss, contact.other))
{
contacts[contact.name] = contact;
}
}
file.close();
std::cout << "File " << filename << " loaded" << std::endl;
}
void ContactBook::saveToFile(const std::string& filename) {
std::ofstream file(filename);
if (!file) {
std::cout << "Unable to create file" << std::endl;
return;
}
for (const auto& pair : contacts) {
const Contact& contact = pair.second;
file << contact.name << '\t' <<
contact.address << '\t' <<
contact.email << '\t' <<
contact.number << '\t' <<
contact.bday << '\t' <<
contact.other << '\n';
}
file.close();
std::cout << "File " << filename << " saved" << std::endl;
}
Otherwise, if you want to use '\n'
to delimit your fields, then you need to get rid of the std::istringstream
and just read the lines from the std::ifstream
directly, eg:
void ContactBook::loadFromFile(const std::string filename) {
std::ifstream file(filename);
if (!file) {
std::cout << "Unable to open file" << std::endl;
return;
}
Contact contact;
while (std::getline(file, contact.name) &&
std::getline(file, contact.address) &&
std::getline(file, contact.email) &&
std::getline(file, contact.number) &&
std::getline(file, contact.bday) &&
std::getline(file, contact.other))
{
contacts[contact.name] = contact;
}
file.close();
std::cout << "File " << filename << " loaded" << std::endl;
}
void ContactBook::saveToFile(const std::string& filename) {
std::ofstream file(filename);
if (!file) {
std::cout << "Unable to create file" << std::endl;
return;
}
for (const auto& pair : contacts) {
const Contact& contact = pair.second;
file << contact.name << '\n' <<
contact.address << '\n' <<
contact.email << '\n' <<
contact.number << '\n' <<
contact.bday << '\n' <<
contact.other << '\n';
}
file.close();
std::cout << "File " << filename << " saved" << std::endl;
}