【问题标题】:Can't load correct information from file无法从文件中加载正确的信息
【发布时间】:2020-02-16 05:48:34
【问题描述】:

这段代码的问题在于它没有正确读取 .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++ FAQ: Serialization and Unserialization,特别是How exactly do I read/write simple types in human-readable (“text”) format?的答案
  • 在二进制模式下,您只是在读取字节。输入函数不关心字节是什么,它只是 8 位。所以像'\n' 这样的格式化字符没什么特别的。它只是一个带有值0xa 的字节被读取。如果您的输入文件是文本(如您所示)并且您关心单独 Lines 中的信息,那么您需要使用将'\n' 识别为分隔符的输入法(或至少作为空格)。所以&gt;&gt;getline 是在普通文本模式下阅读的正常选择。
  • 格式说明: 今后不要发布文本图片,而是复制文本并将其粘贴到您的问题中(缩进 4 个空格,因此格式为固定宽度,或者在上面/下面添加由```(3个反引号)组成的行,其格式也将固定)

标签: c++ fstream


【解决方案1】:

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

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

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

您可以通过重载&lt;&lt;&gt;&gt; 运算符以从您的输入流中以文本形式一次读取一个学生的数据,从而使学生数据的读取和输出变得更加简单。例如,要重载 &lt;&lt; 运算符以读取 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(供您收集 @987654341 @ 而不是一个普通的结构数组)等。对于您的类,您将使用一个额外的容器来强制使用唯一的verfID。您可以自己编写一个函数,在每次插入新学生之前扫描所有收集的student_t,或者您可以使用std::unordered_set 以更有效的方式为您完成。

因此,使用 STL 容器,您只需要一个 std::vector&lt;student_t&gt; 来存储您的学生信息(而不是一个数组),并且您可以使用 std::unordered_set&lt;int&gt; 来散列您的 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: 成员,您可以使用将要读取的文件名作为参数的构造函数,然后除了重载的&lt;&lt;&gt;&gt; 运算符之外,您只需要一个辅助函数。辅助函数只是循环使用重载的 &gt;&gt; 运算符获取输入,直到到达文件末尾。

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

  public:

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

重复使用&gt;&gt; 运算符的辅助函数可以是:

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

其余细节由重载的&lt;&lt;&gt;&gt; 运算符处理。从&lt;&lt; 的重载开始,你真的不需要它做任何事情,只需要遍历所有学生并以你喜欢的格式输出数据,例如

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

&gt;&gt; 的重载是大部分工作发生的地方,尽管逻辑很简单。您声明一个临时的 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 提供的容器是更好的方法,而不是尝试自己重新发明轮子(这样可以消除很多错误......)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-07
    • 2019-08-25
    • 1970-01-01
    • 2012-09-24
    • 2017-11-17
    • 1970-01-01
    • 1970-01-01
    • 2022-12-03
    相关资源
    最近更新 更多