【问题标题】:creating a class vector that does not delete it's content创建一个不删除其内容的类向量
【发布时间】:2022-01-22 16:50:45
【问题描述】:

我是初学者,所以我想问一下,我们可以创建一个类对象向量/数组,当我关闭程序时不会删除它的内容,就像我想要一个客户记录一样,但是每当我们尝试要重新启动程序,我们需要一次又一次地输入客户详细信息...... 如何防止这种情况发生

#include <iostream>
#include <vector>

using namespace std;
class customer{

    public:
    int balance;

    string name;
    int password;
};
int main(){
    vector <customer> cus;
    

    ... 

    if(choice == 1){
        cout << cus[i].balance
    }
    return 0;
}

【问题讨论】:

  • 您需要以一种或另一种方式序列化数据
  • 将数据写入文件并在程序启动时读取。
  • 答案是是的,我们可以!。但是您必须将容器备份到磁盘文件上。一种相当简单的方法是将容器的内容存储到析构函数中的磁盘文件中,然后将其加载回构造函数中。极端情况是文件仍然不存在时容器的初始创建。了解序列化可能是一个有趣的问题...

标签: c++ c++11


【解决方案1】:

当程序员创建vector 类时,他必须确保为该向量获取的资源在不再需要时释放。 (见 RAII)

C++ 参考:https://en.cppreference.com/w/cpp/language/raii

维基百科:https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization

堆栈溢出:What is meant by Resource Acquisition is Initialization (RAII)?

微软:https://docs.microsoft.com/en-us/cpp/cpp/object-lifetime-and-resource-management-modern-cpp?view=msvc-170

在程序关闭之前,必须释放所有资源。 (没有泄漏资源,包括内存)

不可能创建一个在关闭程序后不删除其内容的向量类。安全操作系统会在程序关闭时释放程序资源。

如果您希望程序在关闭后不丢失客户信息,您需要将信息保存在永久性(非易失性)存储设备中,例如磁盘。

正如 CinCout、김선달、Serge Ballesta 所说,您必须将客户信息保存到文件中,并编写程序以便在程序启动期间读取该文件。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

struct customer {

    std::string name;
    int balance;
    int password;

};

int main() {
    
    std::vector <customer> customers;
    std::ifstream ifs("info.txt");
    {

        customer customer{};

        while (ifs >> customer.name >> customer.balance >> customer.password)
            customers.push_back(customer);

    }

    for (const auto& [name, balance, password] : customers) {

        std::cout <<
            "\nName     : " << name     <<
            "\nBalance  : " << balance  <<
            "\nPassword : " << password <<
            '\n';

    }



    std::cout << "\n\nWelcome\n\n";

    std::ofstream ofs("info.txt", std::ios_base::app);

    char cont{};

    do {

        customer customer{};

        std::cout << "Name     : ";
        std::cin >> customer.name;

        std::cout << "Balance  : ";
        std::cin >> customer.balance;

        std::cout << "Password : ";
        std::cin >> customer.password;

        ofs << customer.name << ' ' << customer.balance << ' ' << customer.password << '\n';

        std::cout << "Add another customer? (Y/N) : ";
        std::cin >> cont;

    } while (cont == 'Y');



    for (const auto& [name, balance, password] : customers) {

        std::cout <<
            "\nName     : " << name     <<
            "\nBalance  : " << balance  <<
            "\nPassword : " << password <<
            '\n';

    }

}

CPlusPlus:https://www.cplusplus.com/doc/tutorial/files/

LearnCpp : https://www.learncpp.com/cpp-tutorial/basic-file-io/

(关于文件 I/O)

这个程序是一个原型,我留下了一些不完整的东西(比如检查读数、用户定义的 I/O 操作符、重复代码、格式化、重新分配客户、在 range-for + 结构化绑定之后不需要 ifs,.. .).

我建议你阅读《编程:使用 C+ 的原理和实践》一书,我正在阅读它,它对我帮助很大。

(我也是初学者)

编辑:我还建议您使用“使用命名空间标准;”仅适用于小型项目、示例或简单练习。 不要使用“使用命名空间标准;”对于实际项目、大型项目或可能包含其他依赖项的项目,因为使用“using namespace std;”可能导致 std 中的名称与其他代码和库的名称之间可能发生命名冲突。

一直使用它不是一个好习惯。

【讨论】:

  • cplusplus.com 是一个糟糕的编码示例,它延续了糟糕的“使用命名空间标准”习惯。如果建议删除链接。
  • 如果只是为了那个那我不能,对于小项目、例子或者简单的练习,使用“using namespace std;”不是问题。我添加 cplusplus 的原因是因为它解释了我在代码中使用的一些东西以及其他有助于改进它的东西。 (就我个人而言,我从不使用“using namespace std”,对我来说,在一些标准库实用程序之前添加 std:: 不是问题,添加五个字符的成本低于使用“using namespace std”可能导致可能的名称冲突的成本标准;")。
  • 当然有sn-p时没问题。但是成千上万的程序员接受了它,认为这是一种很好的做法并继续使用它。至少,添加免责声明。我在 SE 上看到的几乎所有代码审查示例都使用它。
  • 好的,谢谢你的建议!
【解决方案2】:

作为对亚当回答的补充,可以将序列化封装在容器类本身中。这是一个简化的例子:

定义将其内容保存到文件的persistent_vector类的头文件:

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <initializer_list>

namespace {
    // Utility functions able to store one element of a trivially copyable type
    template <class T>
    std::ostream& store1(std::ostream& out, const T& val) {
        out.write(reinterpret_cast<const char*>(&val), sizeof(val));
        return out;
    }

    template <class T>
    std::istream& load1(std::istream& in, T& val) {
        in.read(reinterpret_cast<char*>(&val), sizeof(val));
        return in;
    }

    // Specialization for the std::string type
    template <>
    std::ostream& store1<std::string>(std::ostream& out, const std::string& val) {
        store1<size_t>(out, val.size());
        if (out) out.write(val.data(), val.size());
        return out;
    }

    template <>
    std::istream& load1<std::string>(std::istream& in, std::string& val) {
        size_t len;
        load1<size_t>(in, len);
        if (in) {
            char* data = new char[len];
            in.read(data, len);
            if (in) val.assign(data, len);
            delete[] data;
        }
        return in;
    }
}

template <class T>
class persistent_vector {
    const std::string path;
    std::vector<T> vec;

    // load the vector from a file
    void load() {
        std::ifstream in(path);
        if (in) {
            for (;;) {
                T elt;
                load1(in, elt);
                if (!in) break;
                vec.push_back(elt);
            }
            if (!in.eof()) {
                throw std::istream::failure("Read error");
            }
            in.close();
        }
    }

    // store the vector to a file
    void store() {
        std::ofstream out(path);
        size_t n = 0;
        if (out) {
            for (const T& elt : vec) {
                store1(out, elt);
                if (!out) break;
                ++n;
            }
        }
        if (!out) {
            std::cerr << "Write error after " << n << " elements on " << vec.size() << '\n';
        }
    }

public:
    // a bunch of constructors, first ones load data from the file
    persistent_vector(const std::string& path) : path(path) {
        load();
    }
    persistent_vector(const std::string& path, size_t sz) :
        path(path), vec(sz) {
        load();
    };
    // last 2 constructors ignore the file because they do receive data
    persistent_vector(const std::string& path, size_t sz, const T& val) :
        path(path), vec(sz, val) {
    };
    persistent_vector(const std::string& path, std::initializer_list<T> ini) :
        path(path), vec(ini) {
    }

    // destructor strores the data to the file before actually destroying it
    ~persistent_vector() {
        store();
    }

    // direct access to the vector (const and non const versions)
    std::vector<T>& data() {
        return vec;
    }
    const std::vector<T>& data() const {
        return vec;
    }
};

它可以开箱即用地处理任何可简单复制的类型和std::string。用户必须为自定义类型提供 store1load1 的特化。

这是一个使用它的简单程序:

#include <iostream>
#include <string>

#include "persistent_vector.h"

int main() {
    std::cout << "Create new vector (0) or read an existing one (1): ";
    int cr;
    std::cin >> cr;
    if (!std::cin || (cr != 0 && cr != 1)) {
        std::cout << "Incorrect input\n";
        return 1;
    }
    if (cr == 0) {
        persistent_vector<std::string> v("foo.data", 0, "");
        // skip to the end of line...
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        for (;;) {
            std::string line;
            std::cout << "Enter a string to add to the vector (empty string to end program)\n";
            std::getline(std::cin, line);
            if (line.empty()) break;
            v.data().push_back(line);
        }
    }
    else {
        persistent_vector<std::string> v("foo.data");
        for (const std::string& i : v.data()) {
            std::cout << i << '\n';
        }
    }
    return 0;
}

【讨论】:

    猜你喜欢
    • 2013-03-14
    • 2020-09-20
    • 2015-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 2021-11-27
    • 2018-06-07
    相关资源
    最近更新 更多