首页 > 解决方案 > 无法从文件中加载正确的信息

问题描述

这段代码的问题在于它没有正确读取 .txt 文件,我提供了 .txt 文件的图像,同时还提供了它给我的当前输出。欢迎任何帮助。

#include <fstream>
#include <iostream>

using namespace std;
const int MAX_CHARS = 10;
const int MAX_STUDENTS = 1;
class File
{
public:
void openFile()
{
    ifstream input_file("UserPass.txt", ios::binary);
    if (input_file.fail())
    {
        cout << "Could not open file" << endl;
    }
    else
    {
        if (!input_file.read((char*)&studLoaded, sizeof(studLoaded)))
        {
            cout << "Could not read file" << endl;
        }
        else
        {
            streamsize bytesRead = input_file.gcount();
            if (bytesRead != sizeof(studLoaded))
            {
                cout << "Could not read expected number of bytes" << endl;
            }
            else
            {
                input_file.read((char*)&studLoaded, sizeof(studLoaded));
                input_file.close();
            }


        }
    }
};

void displayFile()
{
    for (size_t i = 0; i < MAX_STUDENTS; i++)
    {
        cout << "Username: " << studLoaded[i].username << endl;
        cout << "Password: " << studLoaded[i].password << endl;
        cout << "Verf ID:" << studLoaded[i].verfID << endl;
    }
}

private:

typedef struct
{
    char username[MAX_CHARS];
    char password[MAX_CHARS];
    int verfID;

}student_t;

student_t studLoaded[MAX_STUDENTS];
};

主要就是调用这些函数

File f;
f.openFile();
f.displayFile(); 

这是 .txt 文件中的内容在此处输入图像描述

这是我目前的输出。我已经尝试了很多东西,但我似乎无法让它发挥作用。这是我得到的当前输出。

在此处输入图像描述

标签: c++fstream

解决方案


继续我上面的评论,鉴于您显示的输入文件是 TEXT 文件,您不想读为ios::binary. 为什么?读取二进制输入时,所有文本格式字符都没有特殊含义。您只是在读取数据字节,并且'\n'(value: 0xa) 只是流中的另一个字节。阅读文本时,您希望使用文本文件中的格式字符来告诉您何时阅读了一行或一个单词。

此外,正如@sheff所评论的那样,您是否以二进制形式读取,您可以事先知道您将读入多少字节username或int 在流中的位置passwordverfID他提供的链接很好地解释了过程C++ FAQ: Serialization and Unserialization。对于写入二进制数据,尤其是当数据位于 a 中时struct,除非您进行序列化,否则编译器之间的可移植性无法保证,因为可能会将填充位插入到结构中。

因此,除非您需要以二进制形式读取和写入,否则最好将文本文件作为文本读取。

您可以通过重载<<and>>运算符从输入流中一次读取一个学生数据作为文本,从而使学生数据的读取和输出变得更加简单。例如,要重载<<运算符以读取student_t数据,您可以简单地向您的类添加一个成员函数:

    /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* handle storage of s here */
        }

        return is;  /* return stream state */
    }

使用重载运算符的好处不仅减少了必须编写的自定义输入函数,而且会大大减少您的main()工作量。例如:

int main (int argc, char **argv) {

    if (argc < 2) { /* verify at least 1 argument for filename */
        std::cerr << "error: password filename required.\n";
        return 1;
    }

    passfile pf (argv[1]);      /* declare instance of class, with filename */
    std::cout << pf;            /* output all student data */
}

要将类的各个部分放在一起,请避免使用基本类型,例如char[CONST]STL 提供的类型,例如std::stringstd::vector(用于您的集合,student_t而不是普通的结构数组)等。对于您的班级,您将使用一个额外的容器来强制使用唯一的verfID. 您可以自己编写一个函数,在student_t每次插入新学生之前扫描所有收集的数据,或者您可以使用std::unordered_set以更有效的方式为您完成此操作。

因此,使用 STL 容器,您只需std::vector<student_t>为您的学生信息存储(而不是数组),您将使用它std::unordered_set<int>来散列您的信息verfID并强制执行唯一性。您的类private:数据成员可能类似于:

class passfile {

    struct student_t {
        std::string username {}, password {};   /* user std:string istead */
        int verfID;
    };

    std::unordered_set<int> verfID;         /* require unique verfID before add */
    std::vector<student_t> students {};     /* use vector of struct for storage */
    ...

对于您的public:成员,您可以使用将要读取的文件名作为参数的构造函数,然后除了重载的<<>>运算符之外,您只需要一个辅助函数。辅助函数只是循环使用重载>>运算符获取输入,直到到达文件末尾。

您的构造函数实际上不需要:

  public:

    passfile() {}
    passfile (std::string fname) { readpwfile (fname); }
    ...

您重复使用运算符的辅助功能>>可以是:

    void readpwfile (std::string fname)     /* read all students from filename */
    {
        std::ifstream f (fname);
        do
            f >> *this;                     /* use overloaded >> for read */
        while (f);
    }
    ...

剩余的细节由重载的<<>>操作符处理。从 的重载开始<<,您真的不需要它做任何事情,只需遍历所有学生并以您喜欢的格式输出数据,例如

    /* overload << to output all student data */
    friend std::ostream& operator << (std::ostream& os, const passfile& pf)
    {
        for (auto s : pf.students)
            os  << "Username: " << s.username << '\n' 
                << "Password: " << s.password << '\n' 
                << "Verf ID : " << s.verfID << "\n\n";

        return os;
    }

注意:类内声明中使用的friend关键字in,如果你在其他地方定义了函数,你会friend在定义之前省略)

尽管逻辑很简单,但您的重载>>是大部分工作发生的地方。您声明一个临时student_t的以从流中读取值。如果成功,您在 unordered_set 中快速查找以查看是否verfID已经存在。如果不是,您将其添加verfID到您的 unordered_set,并将您的临时添加student_t到您的向量中,您就完成了。如果verfID是重复的,您可以发出警告或错误,例如

     /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* if verfID not already in verfID unordered_set */
            if (pf.verfID.find (s.verfID) == pf.verfID.end()) {
                pf.verfID.insert (s.verfID);    /* add verfID to unordered_set */
                pf.students.push_back (s);      /* add temp student to vector */
            }
            else    /* warn on duplicate verfID */
                std::cerr << "error: duplicate verfID " << s.verfID << ".\n";
        }

        return is;  /* return stream state */
    }

将其完全放在一个简短的示例中(基本上只是添加标题并关闭上述信息的类),您将拥有:

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

class passfile {

    struct student_t {
        std::string username {}, password {};   /* user std:string istead */
        int verfID;
    };

    std::unordered_set<int> verfID;         /* require unique verfID before add */
    std::vector<student_t> students {};     /* use vector of struct for storage */

  public:

    passfile() {}
    passfile (std::string fname) { readpwfile (fname); }

    void readpwfile (std::string fname)     /* read all students from filename */
    {
        std::ifstream f (fname);
        do
            f >> *this;                     /* use overloaded >> for read */
        while (f);
    }

    /* overload >> to read username, password, verfID from input stream */
    friend std::istream& operator >> (std::istream& is, passfile& pf)
    {
        student_t s {};     /* temporary struct student */

        /* attempt read of all 3 values (username, password, verfID) */
        if (is >> s.username >> s.password >> s.verfID) {
            /* if verfID not already in verfID unordered_set */
            if (pf.verfID.find (s.verfID) == pf.verfID.end()) {
                pf.verfID.insert (s.verfID);    /* add verfID to unordered_set */
                pf.students.push_back (s);      /* add temp student to vector */
            }
            else    /* warn on duplicate verfID */
                std::cerr << "error: duplicate verfID " << s.verfID << ".\n";
        }

        return is;  /* return stream state */
    }

    /* overload << to output all student data */
    friend std::ostream& operator << (std::ostream& os, const passfile& pf)
    {
        for (auto s : pf.students)
            os  << "Username: " << s.username << '\n' 
                << "Password: " << s.password << '\n' 
                << "Verf ID : " << s.verfID << "\n\n";

        return os;
    }
};

int main (int argc, char **argv) {

    if (argc < 2) { /* verify at least 1 argument for filename */
        std::cerr << "error: password filename required.\n";
        return 1;
    }

    passfile pf (argv[1]);      /* declare instance of class, with filename */
    std::cout << pf;            /* output all student data */
}

示例输入文件

使用上面的输入文件作为 TEXT 文件:

$ cat dat/userpass.txt
Adam
Pass121
1
Jamie
abc1
2

示例使用/输出

运行程序并提供您的输入文件作为第一个参数将导致:

$ ./bin/passwdfile dat/userpass.txt
Username: Adam
Password: Pass121
Verf ID : 1

Username: Jamie
Password: abc1
Verf ID : 2

如果您需要通过提示用户输入信息来添加更多学生,那么只需要:

    std::cout << "enter user pass verfID: ";
    std::cin >> pf;

(尝试一下,并尝试添加重复的verfID...)

如果您还有其他问题,请仔细查看并告诉我。到目前为止,使用 STL 提供的容器是更好的方法,而不是尝试自己重新发明轮子(这样可以消除很多错误......)


推荐阅读