首页 > 解决方案 > 替换文件中的金额并将其保存到文件中

问题描述

我正在尝试对文本文件进行更改,然后将它们保存到文件中。例如,如果我们有包含此信息的文本文件:

用户 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;
}

标签: c++

解决方案


你已经起草了一个很好的第一个版本。

现在,您的下一个问题是,如何保存修改后的数据。这对于文本文件来说并不容易。基本上只有在特殊情况下才几乎不可能。如果您不使用可以逐条存储数据的数据库,那么推荐的一种方法是:

  • 读取内存中的所有数据(您已经这样做了)
  • 修改数据(也是你已经做过的)
  • 通过覆盖现有文件(因此,在您的文件关闭语句之后)将数据保存在您的文件中。在这里,你有两种可能
  • 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;
}

顺便说一句,您还试图溢出一个由“;”分隔的字符串

将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。都有不同的属性。有些难以理解,有些难以开发,有些更复杂,更慢或更快或更灵活或不灵活。

备择方案

  1. 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
  2. 使用旧式std::strtok功能。也许不安全。也许不应该再使用了
  3. std::getline. 最常用的实现。但实际上是一种“误用”,并没有那么灵活
  4. 使用专门为此目的开发的专用现代功能,最灵活且最适合 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;
}

推荐阅读