You already drafted a good 1st version.
Now, your next question is, how to save the modified data. This is not that easy with text files. Basically it is only hardly possible under special circumstances. If you do not use databases, where the data can be stored record by record, then one recommended approach would be:
- Read all data in memory (you did that already)
- Modify the data (alsothis you did already)
- Save the data in your file by overwriting the existent file (so, after your file close statement). Here, you have 2 possibilities
- a.) Simply open your file again, this time for output, and then overrite it by simply outputting the new data.
- b.) Open a temp file, write the modfied data to the temp file (you know, if that worked or not), then delete the original file and rename the tempfile to the original file name
Option b.) is a little bit safer.
Let me give to you a general example (unrelated to your problem) on how to do that:
Option a.)
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
std::vector<std::string> readFile(const std::string& filename) {
// Here we will store all the data from the file
std::vector<std::string> fileData;
// Open the source file
std::ifstream fileStream(filename);
// Read line by line and add it to our fileData
std::string line;
while (std::getline(fileStream, line)) {
fileData.push_back(line);
}
return fileData;
}
void writeFile(std::vector<std::string>& fileData, const std::string& filename) {
// Open file for output
std::ofstream fileStream(filename);
// Write all data to file
for (const std::string& line : fileData)
fileStream << line << '
';
}
int main() {
// Aproach with read complete file to local variable, modify and the store again
const std::string dataFileName("r:\test.txt");
// Get file content
std::vector<std::string> data = readFile(dataFileName);
// Now go through all records and do something
for (std::string& line : data) {
// If some condition is met then do something, for example modify
if (line == "Line1") line += " modified";
}
// And then write the new data to the file
writeFile(data, dataFileName);
return 0;
}
And for option b.)
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
int main() {
// Aproach with temp file, remove and rename, and on the fly change
const std::string dataFileName("r:\test.txt");
const std::string tempFileName("r:\temp.txt");
bool writingtoTempFileWasOK = true;
{
// Open the source file with data
std::ifstream dataFileStream(dataFileName);
// Open the temporary file for output
std::ofstream tempFileStream(tempFileName);
// Now read the source file line by line with a simple for loop
std::string line;
while (std::getline(dataFileStream, line) && writingtoTempFileWasOK) {
// Identify the line that should be deleted and do NOT write it to the temp file
if (line != "SearchString") { // Or any other condition
// Write only, if the condition is not met
if (not (tempFileStream << line << '
'))
writingtoTempFileWasOK = false;
}
}
} // The end of the scope for the streams, will call their destructor and close the files
// Now, remove and rename
if (writingtoTempFileWasOK) {
std::remove(dataFileName.c_str());
std::rename(tempFileName.c_str(), dataFileName.c_str());
}
return 0;
}
Then, after your line balance[0] -= 200;
, you could then reopen the file and store again all data by writing (using option .a) something like
if (std::ofstream ofs("names.txt"); ofs) {
for (size_t i{}; i < data.size(); ++i)
ofs << . . . // Whatever you want
}
But, I would not do this at all.
In my opinion you should refactor your design.
At the moment you are using 2 different std::vector
to store data, that belong togehter. That may lead to problems with synchronizing both std::vectors
. And, you are missing the information about transactions (Modification of the amount).
In C++, we normally group the info in a struct, and then write methords, to work on such info.
I will show you a C++ solution, using modern C++17 elements, on how such an object oriented approach could be done. This is maybe to advanced, but you could get an idea for your own design and implementation.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <cstdio>
struct Balance {
// The data that we want to use
std::string user{};
double amount{};
// Extractor operator. Extract data from a stream (Read the values)
friend std::istream& operator >> (std::istream& is, Balance& b) {
// Read a complete line and check, if that worked
if (std::string line{}; std::getline(is, line))
if (line.empty()) {
is.setstate(std::ios_base::failbit);
}
else
{
// Pack the line in a std::istringstream in order to be able to extract the user name from a string
if (std::istringstream iss{ line }; std::getline(iss, b.user, ';'))
// If reading of user name worked then read amount
iss >> b.amount;
}
return is;
}
// Inserter operator. Write data to an ostream. (Save data values)
friend std::ostream& operator << (std::ostream& os, const Balance& b) {
return os << b.user << ';' << b.amount;
}
};
int main() {
const std::string sourceFileName{ "r:\names.txt" };
const std::string tempFileName{ "r:\temp.txt" };
// Here we will store all our data
std::vector<Balance> balance{};
bool everythingOk{ false };
// Open temp file name, and check, if that worked
if (std::ofstream tempFileStream(tempFileName); tempFileStream) {
// Open source file with names and amounts and check if that worked
if (std::ifstream sourceFileStream(sourceFileName); sourceFileStream) {
// Read/copy the complete source file and assign our internal data values
std::copy(std::istream_iterator<Balance>(sourceFileStream), {}, std::back_inserter(balance));
// For debug purposes, show result on screen
std::copy(balance.begin(), balance.end(), std::ostream_iterator<Balance>(std::cout, "
"));
// Modifiy some value
balance[0].amount -= 200;
// Write everything to tempfile
std::copy(balance.begin(), balance.end(), std::ostream_iterator<Balance>(tempFileStream, "
"));
everythingOk = tempFileStream.good() ;
} // End of scope for if. Destructor for sourceFileStream will be called. This will close the source file
else std::cerr << "
Error: Could not open source file '" << sourceFileName << "'
";
}
else std::cerr << "
Error: Could not open temp file '" << tempFileName << "'
";
if (everythingOk) {
std::remove(sourceFileName.c_str());
std::rename(tempFileName.c_str(), sourceFileName.c_str());
}
return 0;
}
By the way, you also tried to spilt a string separated by a ';'
Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
- Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
- Using old style
std::strtok
function. Maybe unsafe. Maybe should not be used any longer
std::getline
. Most used implementation. But actually a "misuse" and not so flexible
- Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '
'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ &qu