【问题标题】:C++ Program crashes when reading object from random access file从随机访问文件中读取对象时 C++ 程序崩溃
【发布时间】:2010-10-30 16:17:41
【问题描述】:

我有以下包含多个属性(字符串)的 User.h。 User.cpp 包含所有定义。

//User.h
#ifndef USER_H
#define USER_H
#include<iostream>
#include <cstring>

using namespace std;

class User{

  string username;

  public:
         User();
         string getUsername() const;                      
         void setUsername(string);

};
#endif

我正在使用另一个类“文件”从随机访问的 .dat 文件中插入新用户/查看用户

//File.h
#ifndef FILE_H
#define FILE_H
#include "User.h"

class File{
    public:

             void loadUser(); 
             bool addUser(User&); 

};
#endif

文件类定义

//File.cpp
#include<cstring>
#include<iostream>
#include<iomanip>
#include<fstream>

#include "User.h"
#include "File.h"

using namespace std;

User tempUser;
fstream usersFile;

void File::loadUser(){
     usersFile.open("users.dat", ios::in | ios::binary);

     usersFile.seekg(0);  

     // commenting the following lines prevented the issue
     usersFile.read(reinterpret_cast<char *>(&tempUser), sizeof(tempUser)); 

     cout<<tempUser.getUsername().c_str(); 

     usersFile.close();
}

bool File::addUser(User& user){

     usersFile.open("users.dat", ios::out | ios::ate | ios::binary);

     // no issue when writing to file
     usersFile.write( reinterpret_cast<const char *>(&user), sizeof(user));

     usersFile.close();

     cout<<"User added";  
}

我在运行时遇到了上述问题。不过没有编译问题。

在处理内部具有“字符串属性”的对象时是否有任何问题?

请帮忙

【问题讨论】:

    标签: c++ file-handling


    【解决方案1】:

    我认为问题在于您将 C++ 代码与 C 思维方式混合在一起。 您真正应该在这里做的是使用提取运算符opeartor&gt;&gt;(),以及C++ IO 流。这与使用 C 标准 IO 和 read() 函数相反。要编写对象,请使用插入运算符 operator&lt;&lt;() 而不是 C 中的 write() 函数。

    要处理字符串,请使用std::string。此类提供operator&lt;&lt;()operator&gt;&gt;()。因此,您可以说std::string s,然后是io &lt;&lt; sio &gt;&gt; s,其中io 是一些C++ IO 流对象。这将做正确的事情(tm)。这里的理念是std::string 类比您(用户)更了解如何序列化std::string 对象。所以让它去做吧,使用 > 操作符。

    继续这个想法,作为User 的作者,您比任何人都更了解如何序列化User 对象。因此,为您的类的用户提供 > 运算符,作为一项服务。 “你班级的用户”很可能是一周后的你,那时你完全忘记了如何正确序列化 User 对象。 (或者,您认为您记得但实际上您忘记了一个细节,从而导致代码中的错误)。示例:

    // in User.h
    #include <string>
    #include <iosfwd>  // forward declarations of standard IO streams
    
    namespace mine {
    class User {
        User(const std::string& name) : username(name) { }
        friend std::ostream& operator<<(std::ostream&, const User&);
        friend std::istream& operator>>(std::istream&, User&);
    
    private:
        std::string username;
    };
    
    std::ostream& operator<<(std::ostream& out, const User& u)
    {
        return out << u.username;
    }
    
    std::istream& operator>>(std::istream& in, User& u)
    {
        return in >> u.username;
    }
    }  // namespace mine
    

    从这里开始,将用户保存到您说的文件中

    std::ofstream f("filename");
    User u("John");
    f << u;
    

    就是这样。读取用户:

    std::ifstream f2("filename");
    f2 >> u;
    

    将代码包装在命名空间中是一种很好的做法。 IDE 通过自动完成功能显示有多少符号可见,从而很好地可视化了这个问题。你会看到全球范围内有多少混乱。通过将代码包装在名称空间中,您可以将其分组在一个范围名称下,从而在全局名称范围中节省更多混乱。就是要整洁。如果您将代码放在自己的命名空间中,那么您可以为函数、类或变量选择任何您想要的名称,只要您之前没有选择过它。如果您不将其放在命名空间中,那么您需要与其他人共享名称。就像臭鼬宣布自己的领地一样,只是没有臭味。

    在那张纸条上,我建议您从标题中删除using namespace std。这会将std 命名空间中的所有符号带入#include 标头的所有文件的范围内。这是一个不好的做法。如果你愿意,只在实现文件中说using namespace std,而不是在头文件中。

    当然,有些人会说即使这是一个坏主意。我个人认为,如果您知道在该特定实现文件中可能存在名称冲突这一事实,那很好。但至少你知道using 语句在哪里:它在你的实现文件中,它只会在那个实现文件中引起冲突。它有点像一把枪,(一把塑料水枪,但仍然是一把枪),只有你只能射(弄湿)自己的脚,而不能射别人的脚。在我看来,这非常好。

    【讨论】:

      【解决方案2】:

      您不能像那样读取非 POD 类型。字符串不是 POD 类型。 What are POD types in C++?

      有几种方法可以正确读取字符串,具体取决于它们的存储方式。

      对于文本文件:

      如果字符串只是一个单词,两边用空格分隔,您可以使用普通的旧 >> 运算符。如果多于一个单词,可以单独存放在一行,使用getline。

      对于二进制文件:

      以空终止形式存储字符串。一次读取一个字符,检查空字符。或者,在字符串前面加上一个整数来存储它的大小。读的时候,先读入整数,再读入那么多字符。

      【讨论】:

        【解决方案3】:

        string 是一个对象,这意味着您不是在编写它的内容。

        尝试编写用户并检查文件以了解我的意思。然后您正在阅读的是一些指针,它们指向无效的内存位置。

        【讨论】:

          【解决方案4】:

          是的,std::string 类不是普通的旧数据,换句话说,它包含指针。 如果以这种方式保存/加载字符串类,指向的数据不会被加载/保存,只有指针的值。

          另外 sizeof(tempUser) 不包括字符串指向的文本的大小。

          您的解决方案是改变您读取/写入数据的方式。 一种方法是使用 boost::serialization 来处理像 std::string 这样的数据类型。 另一种方法是自己将每个字符串写入文本文档中的单独行(不是二进制模式),然后使用 readline 将它们读回。

          【讨论】:

            【解决方案5】:

            而不是构建您自己的自定义序列化代码,Google Protocol Buffers 之类的东西可能会更轻松地完成您想要的事情。它非常适合将简单的结构化数据从一个地方传递到另一个地方。

            【讨论】:

              猜你喜欢
              • 2011-11-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-04-05
              相关资源
              最近更新 更多