2010-10-30 87 views
0

我有以下User.h,它包含几个属性(字符串)。 User.cpp具有所有的定义。当从随机访问文件中读取对象时,C++程序崩溃

//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"; 
} 

我插入新用户/用户视图运行时遇到上述问题。虽然没有编译问题。

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

请帮

回答

1

我认为问题在于你正在将C++代码与C心态混合在一起。 你应该在这里做的是使用提取操作符opeartor>>()以及C++ IO流。这与使用C标准IO以及read()函数相反。要写入对象,请使用插入运算符operator<<()而不是C write()函数。

使用字符串使用std::string。本课程提供operator<<()operator>>()。因此,您可以说std::string s,然后io << sio >> 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名称空间中的所有符号都纳入了标题中所有文件的范围。这是一个不好的做法。如果你愿意,只能在实现文件中说using namespace std,但不能在头文件中说。当然,有些人会说这是一个坏主意。我个人认为,如果你意识到在特定的实现文件中可能存在名称冲突的事实,那很好。但至少你知道using声明的位置:它在你的实现文件中,并且只会导致该实现文件中的冲突。这是一种枪,(一个塑料水枪,但仍然是一把枪),只有你可以射击(湿)你自己的脚,没有其他人。在我看来,这是完全正确的。

0

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

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

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

0

字符串是一个对象,这意味着您没有写入它的内容。

尝试编写一个用户并检查该文件以查看我的意思。你正在阅读的是一些指针,指向无效的内存位置。

0

而不是构建自己的自定义序列化代码,像Google Protocol Buffers可能会做你想要的东西,用较少的努力。它非常适合将简单的结构化数据从一个地方传递到另一个地方。

1

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

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

对于文本文件:

如果字符串就是一个字,用空格两侧分开,你可以使用普通的老式>>操作符。如果它不止一个单词,则可以将其存储在自己的行中,并使用getline。

对于二进制文件:

存放在空字符串终止形式。一次读取一个字符,检查空字符。或者,用一个存储它的大小的整数前置该字符串。当你读它时,首先读入整数,然后读入那么多字符。

相关问题