无法从文件中加载正确的信息

Can't load correct information from file

这段代码的问题是它没有正确读取 .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 文件中的内容

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

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

此外,正如 @sheff were you to read in binary, it is up to you to know beforehand how many bytes you will read into username or password and where the verfID int is located in the stream. The link he provided gives a good explanation of the process C++ FAQ: Serialization and Unserialization 评论的那样。对于写入二进制数据,尤其是当数据位于 struct 中时,除非您进行序列化,否则无法保证编译器之间的可移植性,因为可能会在结构中插入填充位。

因此,除非您有读写二进制的要求,否则最好将文本文件作为文本读取。

您可以通过重载 <<>> 运算符从您的输入流中以文本形式一次读取学生数据,从而使学生数据的读取和输出变得更加简单。例如要重载 << 运算符以读取 student_t 数据,您可以简单地向 class:

添加一个成员函数
    /* 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 */
}

要将 class 的各个部分放在一起,请避免使用 基本类型 ,例如 char[CONST],而是使用 STL 提供的 [=59] =] 以更有效的方式为您完成。

因此使用 STL 容器,您只需要一个 std::vector<student_t> 来存储您的学生信息(而不是一个数组),您将使用 std::unordered_set<int> 来散列您的 verfID 和强制唯一性。您的 class 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: 成员,您可以使用一个构造函数,该构造函数将要读取的文件名作为参数,然后除了重载的 [=28] 之外,您只需要一个 helper-function =] 和 >> 运算符。 helper-function 只是循环使用重载的 >> 运算符进行输入,直到到达文件末尾。

您的构造函数真的不需要超过:

  public:

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

要重复使用 >> 运算符的 helper-function 可以是:

    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 关键字用在 class 的声明中,如果你在别处定义函数,你会省略 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 */
    }

将它放在一个简短的示例中(基本上只是添加 headers 并关闭 class 以获取上述信息),您将拥有:

#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 */
}

示例输入文件

将上面的输入文件用作文本文件:

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

示例Use/Output

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

$ ./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 提供的容器是迄今为止更好的方法,而不是尝试自己重新发明轮子(这样可以消除很多错误...)