c++ - 无法从文件加载正确的信息

标签 c++ fstream

这段代码的问题是它无法正确读取.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文件enter image description here中的内容

这是我当前的输出。我已经尝试了很多事情,但是我似乎无法使它正常工作。这是我得到的当前输出。

enter image description here

最佳答案

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

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

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

通过重载<<>>运算符,可以一次从输入流中以文本形式读取学生的大量数据,从而使学生数据的读取和输出更加简单。例如,要重载<<运算符以读取数据的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集合而不是普通的struct数组)之类的东西。对于您的类,您将另外一个容器来强制使用唯一的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:成员,您可以使用一个构造函数,该构造函数以从中读取文件名作为参数,然后,除了重载的<<>>运算符之外,您将仅需要一个辅助函数。 helper-function只是循环使用重载的>>运算符进行输入,直到到达文件末尾。

实际上,您的构造函数不需要:
  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关键字,如果在其他位置定义了函数,则在定义之前将friend省略)

尽管逻辑很简单,但是>>的重载是大部分工作的发生地。您声明一个临时的student_t以从流中读取值。如果成功,则在unordered_set中进行快速查找以查看verfID是否已经存在。如果不是,则将verfID添加到您的unordered_set中,并将您的临时student_t添加到您的 vector 中,然后完成。如果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提供的容器是更好的方法,而不是自己尝试重新发明轮子(这样可以避免很多错误...)

关于c++ - 无法从文件加载正确的信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60245777/

相关文章:

c++ - 模乘反函数不适用于负数

c++ - 在 decltype 中使用命名空间

c++ - 关闭 ifstream 后 vector 下标超出范围

c++ - 仅覆盖文件的特定部分

c++ - fstream C++ 路径

c++ - cpp 中的宏编译错误 (Visual Studio 2012)

c# - 如何在 C# 中实现 BN_num_bytes() (和 BN_num_bits() )?

c++ - 为什么我的 For 循环没有完成收集数据以保存在动态分配的数组中?

c++ - 将 .getline 与字符串而不是字符一起使用

c++ - 如何在 C++ 中解析包含 -0x1.0c7474fp+8 形式的十六进制文件?