c++ - 替换文件中的金额并将其保存到文件中
问题描述
我正在尝试对文本文件进行更改,然后将它们保存到文件中。例如,如果我们有包含此信息的文本文件:
用户 1;2000
用户 2;3000
我想将 2000 更改为 1800 并将其保存到文件中。我怎样才能做到这一点?这是我到目前为止所拥有的:
#include <fstream>
#include <vector>
#include <string>
int main() {
std::ifstream file;
file.open("names.txt", std::fstream::in);
std::vector<std::string>data;//to store all lines
std::vector<double>balance;//to store amounts
if(file.is_open()) {
std::string line;
while(getline(file, line)) {
data.push_back(line);
int find;
find = line.find(';');
std::string amountString = line.substr(find+1, line.size());
double amount = stod(amountString);
balance.push_back(amount);
}
}
file.close();
balance[0] -= 200;
return 0;
}
解决方案
你已经起草了一个很好的第一个版本。
现在,您的下一个问题是,如何保存修改后的数据。这对于文本文件来说并不容易。基本上只有在特殊情况下才几乎不可能。如果您不使用可以逐条存储数据的数据库,那么推荐的一种方法是:
- 读取内存中的所有数据(您已经这样做了)
- 修改数据(也是你已经做过的)
- 通过覆盖现有文件(因此,在您的文件关闭语句之后)将数据保存在您的文件中。在这里,你有两种可能
- a.) 只需再次打开您的文件,这次是为了输出,然后通过简单地输出新数据来覆盖它。
- b.)打开一个临时文件,将修改后的数据写入临时文件(你知道,如果这有效与否),然后删除原始文件并将临时文件重命名为原始文件名
选项 b.) 更安全一些。
让我给你一个关于如何做到这一点的一般例子(与你的问题无关):
选项 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 << '\n';
}
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;
}
对于选项 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 << '\n'))
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;
}
然后,在您的 line 之后balance[0] -= 200;
,您可以重新打开文件并通过写入(使用选项 .a)类似的内容再次存储所有数据
if (std::ofstream ofs("names.txt"); ofs) {
for (size_t i{}; i < data.size(); ++i)
ofs << . . . // Whatever you want
}
但是,我根本不会这样做。
在我看来,你应该重构你的设计。
目前,您正在使用 2 种不同std::vector
的方式来存储属于 togehter 的数据。这可能会导致两者同步出现问题std::vectors
。而且,您缺少有关交易的信息(修改金额)。
在 C++ 中,我们通常将信息分组到一个结构中,然后编写方法来处理这些信息。
我将向您展示一个使用现代 C++17 元素的 C++ 解决方案,说明如何实现这种面向对象的方法。这可能是高级的,但您可以对自己的设计和实现有所了解。
#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, "\n"));
// Modifiy some value
balance[0].amount -= 200;
// Write everything to tempfile
std::copy(balance.begin(), balance.end(), std::ostream_iterator<Balance>(tempFileStream, "\n"));
everythingOk = tempFileStream.good() ;
} // End of scope for if. Destructor for sourceFileStream will be called. This will close the source file
else std::cerr << "\n\nError: Could not open source file '" << sourceFileName << "'\n\n";
}
else std::cerr << "\n\nError: Could not open temp file '" << tempFileName << "'\n\n";
if (everythingOk) {
std::remove(sourceFileName.c_str());
std::rename(tempFileName.c_str(), sourceFileName.c_str());
}
return 0;
}
顺便说一句,您还试图溢出一个由“;”分隔的字符串
将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。都有不同的属性。有些难以理解,有些难以开发,有些更复杂,更慢或更快或更灵活或不灵活。
备择方案
- 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
- 使用旧式
std::strtok
功能。也许不安全。也许不应该再使用了 std::getline
. 最常用的实现。但实际上是一种“误用”,并没有那么灵活- 使用专门为此目的开发的专用现代功能,最灵活且最适合 STL 环境和算法环境。但是比较慢。
请在一段代码中查看 4 个示例。
#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 << '\n'; };
// 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{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
推荐阅读
- r - R中矩阵的递归
- webkit - WebKit2.WebView(和 Gnome 浏览器)不播放 YouTube 视频
- react-native - 找不到函数`visibleFeatures`
- python - 在 windows10 上安装 pytorch/tensorflow 时访问被拒绝
- google-apps-script - 如何在 google appscripts 中自动更新代码更改
- reactjs - 基于 Context 属性的有状态值未更新
- modelica - 什么会影响 Modelica 中的仿真运行时
- r - 如何组合由自动绘图创建的两个图形?
- java - 从 args (java) 读取文件时捕获错误
- java - 使用JAVA在arraylist的for循环中每x秒执行一条指令